Ajax library with XHR2, promises and request limit

Overview

qwest 4.5.0

I finally decided to archive this repository.

At first, I developed Qwest because at the time other libraries were lacking of a proper XHR2 support, concurrent requests, IE8+ support, with a small footprint. The Qwest adventure was a great one but with the emergence of the Fetch API, the Axios.js library and the fact that I do not have much time to maintain it since a few years, I came to the conclusion that the project needed an end.

I advice you to stop using this library as it won't be maintained anymore and use fetch with p-limit to achieve concurrent requests with an elegant and native API. Fetch is still a work in progress but has good support amongst browsers and can be extended with many libraries.

That's all folks!


Qwest is a simple ajax library based on promises and that supports XmlHttpRequest2 special data like ArrayBuffer, Blob and FormData.

Install

npm install qwest
bower install qwest

Qwest is also available via CDNJS : https://cdnjs.com/libraries/qwest

If you need to import qwest in TypeScript, do :

import * as qwest from 'qwest';

Quick examples

qwest.get('example.com')
     .then(function(xhr, response) {
        alert(response);
     });
qwest.post('example.com', {
        firstname: 'Pedro',
        lastname: 'Sanchez',
        age: 30
     })
     .then(function(xhr, response) {
        // Make some useful actions
     })
     .catch(function(e, xhr, response) {
        // Process the error
     });

Basics

qwest.`method`(`url`, `data`, `options`, `before`)
     .then(function(xhr, response) {
        // Run when the request is successful
     })
     .catch(function(e, xhr, response) {
        // Process the error
     })
     .complete(function() {
         // Always run
     });

The method is either get, post, put or delete. The data parameter can be a multi-dimensional array or object, a string, an ArrayBuffer, a Blob, etc... If you don't want to pass any data but specify some options, set data to null.

The available options are :

  • dataType : post (by default), queryString, json, text, arraybuffer, blob, document or formdata (you shouldn't need to specify XHR2 types since they're automatically detected)
  • responseType : the response type; either auto (default), json, xml, text, arraybuffer, blob or document
  • cache : browser caching; default is false
  • async : true (default) or false; used to make asynchronous or synchronous requests
  • user : the user to access to the URL, if needed
  • password : the password to access to the URL, if needed
  • headers : javascript object containing headers to be sent
  • withCredentials : false by default; sends credentials with your XHR2 request (more info in that post)
  • timeout : the timeout for the request in ms; 30000 by default (allowed only in async mode)
  • attempts : the total number of times to attempt the request through timeouts; 1 by default; if you want to remove the limit set it to null

You can change the default data type with :

qwest.setDefaultDataType('json');

If you want to make a call with another HTTP method, you can use the map() function :

qwest.map('PATCH', 'example.com')
     .then(function() {
         // Blah blah
     });

If you need to do a sync request, you must call send() at the end of your promise :

qwest.get('example.com', {async: false})
     .then(function() {
         // Blah blah
     })
     .send();

Since service APIs often need the same type of request, you can set default options for all of your requests with :

qwest.setDefaultOptions({
    dataType: 'arraybuffer',
    responseType: 'json',
    headers: {
        'My-Header': 'Some-Value'
    }
});

Note : if you want to send your data as a query string parameter chain, pass queryString to the dataType option.

Group requests

Sometimes we need to call several requests and execute some tasks after all of them are completed. You can simply do it by chaining your requests like :

qwest.get('example.com/articles')
     .get('example.com/users')
     .post('example.com/login', auth_data)
     .then(function(values) {
         /*
            Prints [ [xhr, response], [xhr, response], [xhr, response] ]
        */
         console.log(values);
     });

If an error is encountered then catch() will be called and all requests will be aborted.

Base URI

You can define a base URI for your requests. The string will be prepended to the other request URIs.

qwest.base = 'http://example.com';

// Will make a request to 'http://example.com/somepage'
qwest.get('/somepage')
     .then(function() {
         // Blah blah
     });

Request limit

One of the greatest qwest functionnalities is the request limit. It avoids browser freezes and server overloads by freeing bandwidth and memory resources when you have a whole bunch of requests to do at the same time. Set the request limit and when the count is reached qwest will stock all further requests and start them when a slot is free.

Let's say we have a gallery with a lot of images to load. We don't want the browser to download all images at the same time to have a faster loading. Let's see how we can do that.

<div class="gallery">
    <img data-src="images/image1.jpg" alt="">
    <img data-src="images/image2.jpg" alt="">
    <img data-src="images/image3.jpg" alt="">
    <img data-src="images/image4.jpg" alt="">
    <img data-src="images/image5.jpg" alt="">
    ...
</div>
// Browsers are limited in number of parallel downloads, setting it to 4 seems fair
qwest.limit(4);

$('.gallery').children().forEach(function() {
    var $this = $(this);
    qwest.get($this.data('src'), {responseType: 'blob'})
         .then(function(xhr, response) {
            $this.attr('src', window.URL.createObjectURL(response));
            $this.fadeIn();
         });
});

If you want to remove the limit, set it to null.

CORS and preflight requests

According to #90 and #99, a CORS request will send a preflight OPTIONS request to the server to know what is allowed and what's not. It's because we're adding a Cache-Control header to handle caching of requests. The simplest way to avoid this OPTIONS request is to set cache option to true. If you want to know more about preflight requests and how to really handle them, read this : https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

Aborting a request

// Start the request
var request = qwest.get('example.com')
                   .then(function(xhr, response) {
                       // Won't be called
                   })
                   .catch(function(xhr, response) {
                       // Won't be called either
                   });

// Some code

request.abort();

Not that only works with asynchroneous requests since synchroneous requests are... synchroneous.

Set options to the XHR object

If you want to apply some manual options to the XHR object, you can use the before option

qwest.get('example.com', null, null, function(xhr) {
        xhr.upload.onprogress = function(e) {
            // Upload in progress
        };
     })
     .then(function(xhr, response) {
        // Blah blah blah
     });

Handling fallbacks

XHR2 is not available on every browser, so, if needed, you can simply verify the XHR version with :

if(qwest.xhr2) {
    // Actions for XHR2
}
else {
    // Actions for XHR1
}

Receiving binary data in older browsers

Getting binary data in legacy browsers needs a trick, as we can read it on MDN. In qwest, that's how we could handle it :

qwest.get('example.com/file', null, null, function(xhr) {
        xhr.overrideMimeType('text\/plain; charset=x-user-defined');
     })
     .then(function(response) {
         // response is now a binary string
     });

Compatibility notes

According to this compatibility table, IE7/8 do not support using catch and delete as method name because these are reserved words. If you want to support those browsers you should write :

qwest.delete('example.com')
     .then(function(){})
     .catch(function(){});

Like this :

qwest['delete']('example.com')
     .then(function(){})
     ['catch'](function(){});

XHR2 does not support arraybuffer, blob and document response types in synchroneous mode.

The CORS object shipped with IE8 and 9 is XDomainRequest. This object does not support PUT and DELETE requests and XHR2 types. Moreover, the getResponseHeader() method is not supported too which is used in the auto mode for detecting the reponse type. Then, the response type automatically fallbacks to json when in auto mode. If you expect another response type, please specify it explicitly. If you want to specify another default response type to fallback in auto mode, you can do it like this :

qwest.setDefaultXdrResponseType('text');

Last notes

  • Blackberry 10.2.0 (and maybe others) can log an error saying json is not supported : set responseType to auto to avoid the issue
  • the catch handler will be executed for status codes different from 2xx; if no data has been received when catch is called, response will be null
  • auto mode is only supported for xml, json and text response types; for arraybuffer, blob and document you'll need to define explicitly the responseType option
  • if the response of your request doesn't return a valid (and recognized) Content-Type header, then you must explicitly set the responseType option
  • the default Content-Type header for a POST request is application/x-www-form-urlencoded, for post and xhr2 data types
  • if you want to set or get raw data, set dataType option to text
  • as stated on StackOverflow, XDomainRequest forbid HTTPS requests from HTTP scheme and vice versa
  • XDomainRequest only supports GET and POST methods

License

MIT license everywhere!

Comments
  • X-Requested-With header

    X-Requested-With header

    As discussed in email I sent the maintainer while GitHub was done, the X-Requested-With header gets mangled because of the code in lines 280-283. Simply removing the code fixes everything, and as far as I can tell, the fix works perfectly in all browsers from IE8 (standards mode) to latest version of other browsers. I am not sure what the intention of the lines in question was, but it doesn't seem to be necessary in any of the browsers I've tested.

    opened by foxbunny 31
  • How to stop appending 'null' to the url?

    How to stop appending 'null' to the url?

    Any request I make, i.e:

    quest.get('https://mysite.com/stuff')
             .then(....);
    

    Ends up calling:

    https://mysite.com/stuff?null

    How can I stop it from adding ?null to the url?

    I forgot to mention, I have set cache: true and using latest version, it still appends the null and prevents caching.

    opened by phillip-haydon 25
  • Cannot find module 'pinkySwear' from '/home/message/www/jsapp/node_modules/qwest/build'

    Cannot find module 'pinkySwear' from '/home/message/www/jsapp/node_modules/qwest/build'

    "qwest": "2.0.3"
    
    [12:44 @ ~/www/jsapp] $ npm install
    [12:44 @ ~/www/jsapp] $ gulp
    [12:44:58] Using gulpfile ~/www/jsapp/gulpfile.js
    [12:44:58] Starting 'lint'...
    [12:44:58] Starting 'linttest'...
    [12:44:58] Starting 'test'...
    [12:44:58] Starting 'browserify'...
    [12:44:58] Starting 'fonts'...
    [12:44:58] Starting 'sprites'...
    [12:44:58] Starting 'html'...
    [12:44:58] Starting 'watch'...
    [12:44:58] Finished 'watch' after 3.74 ms
    [12:44:58] Starting 'server'...
    [12:44:58] Finished 'server' after 6.2 ms
    [12:44:58] Server started http://localhost:8000
    [12:44:58] LiveReload started on port 35929
    [12:44:59] Finished 'html' after 307 ms
    debugger listening on port 5858
    [12:45:00] Finished 'linttest' after 1.7 s
    [12:45:00] Finished 'test' after 1.7 s
    [12:45:01] Finished 'sprites' after 2.71 s
    [12:45:01] Starting 'styles'...
    [12:45:01] Finished 'fonts' after 2.84 s
    [12:45:01] Finished 'styles' after 448 ms
    [12:45:01] Starting 'images'...
    [12:45:05] Finished 'images' after 3.32 s
    [12:45:05] Finished 'lint' after 7.03 s
    [12:45:06] [Error]: Cannot find module 'pinkySwear' from '/home/message/www/jsapp/node_modules/qwest/build' (undefined)
    [12:45:06] Finished 'browserify' after 7.36 s
    [12:45:06] Starting 'build'...
    [12:45:06] Finished 'build' after 690 μs
    [12:45:06] Starting 'default'...
    [12:45:06] Finished 'default' after 5.03 μs
    
    opened by message 23
  • OPTIONS issue

    OPTIONS issue

    qwest.get(my_IP')
         .then(function(xhr, response) {
            alert(response);
         });
    
    

    Raise an error Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource

    How can I add "Access-Control-Allow-Origin: *" in headers ?

    opened by solisoft 19
  • Calling abort on queued requests not working

    Calling abort on queued requests not working

    Hi! First of all, congrats on this amazing library.

    Now my issue: If i set qwest.limit to something different than null, all queued requests remain available after calling xhr.abort() on them.

    I would expect that aborted requests on the queue must also be canceled, even if they are not triggered yet. Is it because they become somehow synchronous requests and that's not supported?

    Thanks

    opened by flgarrid 18
  • How can i break the executing `then` or `catch`?

    How can i break the executing `then` or `catch`?

    I have code:

    // requester.js
    
    function get(url) {
      return Qwest.get(url).catch((xhr, e, resp) => {
        // here i need «filter» error
        // example:
        if (e.error == 400) {
          return true;
        }
        else {
          ErrorHandler.raise(xhr, e, resp, e.error); // my special error handler
          return false; // after `false` next `then` not be executed
          // or this.break() ?
        }
      });
    }
    
    // account_action.js
    
    function LoadAccount() {
      Requester.get('/account/')
      .then((x, acc) => {
       // save account here
      })
      .catch((x, err, r) => {
        // here i want catch only `400` errors
      });
    }
    

    sorry for my English

    opened by sergeysova 17
  • POST header

    POST header

    How do I send a couple of headers with a post? I've tried a couple of things but the all give me an error back. I've tried this one: this.setRequestHeader('content-type' , 'application/json'); this.setRequestHeader('Accept', 'application/json'); this.setRequestHeader('Authorization', 'bearer 433cfb7899'); And this one: this.setRequestHeader('content-type,application/json', 'Accept,application/json', 'Authorization,bearer 433cfb7899');

    opened by JustinLek 16
  • Dependencies not installed

    Dependencies not installed

    Latest version fails to install properly. Specifically pinkySwear is not defined. Presumably this is because it's listed as a devDependency and not a 'normal' dependency in the package.json file.

    opened by willdady 15
  • Development/full version of qwest should work with full versions of dependencies

    Development/full version of qwest should work with full versions of dependencies

    IMHO, if I have amd config,

    require.config({
      baseUrl: '/script',
      ...
      paths: {
          ...
          'qwest': ['lib/qwest'],
          'pinkyswear': ['lib/pinkyswear'],
          'jquery-param': ['lib/jquery-param']
      },
      ...
      shim: {
        ...
        'pinkyswear': {
          exports: 'pinkySwear'
        },
        'qwest': {
          deps: ['jquery-param', 'pinkyswear']
        }
      }
    });
    

    and the paths point to the full versions of the files, the library should work without requiring a build.

    Actual:

    Uncaught ReferenceError: PINKYSWEAR is not defined
    
    opened by shwoodard 14
  • undefined xhr and response objects on last catch

    undefined xhr and response objects on last catch

    Hello,

    I chain queries as follows. The last one returns an error 400 with a JSON object in response that provides detailed information. This response should be available in the last catch but it is undefined, as well as the xhr object:

    var qwestOpts = {
      cache: true,
      withCredentials: true
    };
    
    qwest.post(..., {...}, qwestOpts)
    
    .then(function() {
      return qwest.get(..., null, qwestOpts);
    })
    
    .then(function(xhr, response) {
      ...
      return qwest.post(..., {...}, qwestOpts);
    })
    
    .then(function(xhr, response) {
      ...
      return qwest.post(..., {...}, qwestOpts) // throws error 400 (Bad Request)
    })
    
    .then(...)
    .catch(function(error, xhr, response) {...}); // xhr === response === undefined
    

    Tested using v4.1.1

    opened by sheymann 13
  • Case `cache` option in POST requests

    Case `cache` option in POST requests

    Is there any use case for cache option in POST requests? If not, then I suggest the default value of cache option to depend on method name, where it would be false by default on GET requests, and true for POST. I'll do a PR so you can see what I mean.

    opened by foxbunny 12
  • Suggestion : warn about error handling

    Suggestion : warn about error handling

    Hi,

    I think it would be a good idea to warn developers that functions called in .then will throw errors in .catch, and errors in functions called in .catch will have to be catched by a try...catch block.

    It might not be an issue for developers with a good understanding of JS, but it can be quite confusing at first when you got swallowed errors.

    Maybe just by updating the examples with something like that :

    .then(function (xhr, response) {
        if (typeof validationCallback === 'function') {
            validationCallback(response);
        }
    })
    
    .catch(function (error, xhr, response) {
        console.error(error);
    
        try {
            if (typeof errorCallback === 'function') {
                errorCallback(error, xhr, response);
            }
        } catch (error) {
            console.error(error);
        }
    });
    

    -- PS : Since it's my first message on this repo, I'd like to thank you and the community for the good work on the library, I'm using qwest a lot and I really appreciate it. 👍

    opened by Flyaku 1
  • Self is undefined

    Self is undefined

    self is undefined at https://github.com/pyrsmk/qwest/blob/master/src/qwest.js#L5. It's throwing a ReferenceError when I try to run tests with no defined window.

    opened by robertervin 3
  • HEAD request ignores 'data' parameter

    HEAD request ignores 'data' parameter

    When I call: qwest.map('GET', 'example.com', {canApprove: true}) it successfully requests 'example.com?canApprove=true'

    But, when I call: qwest.map('HEAD', 'example.com', {canApprove: true}) it requests 'example.com' with no query string

    It looks like 'data' parameter is ignore for HEAD request.

    opened by MedFermi 5
  • { responseType:

    { responseType: "document" } not supported correctly

    Test case:

    // expected behavior using XHR:
    var myPartial = null
    var xhr = new XMLHttpRequest()
    xhr.open('GET', '/my/partial.html')
    xhr.responseType = 'document'
    xhr.onreadystatechange = function () { if (xhr.readyState == 4) myPartial = xhr.responseXML }
    xhr.send()
    
    // after a second...
    myPartial.body instanceof HTMLElement // => true
    // all is well.
    
    // -----------------------------------
    
    // behavior using qwest:
    qwest.get('/my/partial.html', null, { responseType: 'document' })
      .then(function (xhr, myPartial) {
        xhr.responseXML // => null?!?!
        myPartial.body.querySelectorAll('div') // Yes, this works...
        myPartial.body instanceof HTMLElement // => false?!?!
        // That's not good. When I try to use this later, exceptions get thrown.
      })
      .catch(function (xhr, error) {
        console.error(error)
      })
    

    I looked at the source code for qwest, and so far as I can tell this makes absolutely no sense. It looks like qwest should just let the responseType pass through into the XMLHttpRequest and everything should just be honky-dory. But instead, the responseXML field is null and the response data, while definitely parsed into DOM, is clearly not quite right since the prototypes don't match.

    bug v5 
    opened by fluffywaffles 6
  • Suggestion to replace PinkySwear to Zousan

    Suggestion to replace PinkySwear to Zousan

    PinkySwear is (probably) not maintained anymore.

    The last update (2014) uses Mocha 1.21.5, that uses Jade (instead of Pug), Graceful-fs 2.0.3 (that will fail on node releases >= v7.0) and a version of MiniMatch that has security vulnerabilities.

    I really like Zousan* but there are also many other implementations and I don't want to be unfair.

    *Because it has no dependencies and still small, works everywhere (browser, node, mobile, etc), it's recent and really well maintained, Promise A+ 1.1 compliant, well documented and incredibly fast.

    I feel that I don't have enough knowledge to make this replacement in Qwest myself. But, if you want, I am available to learn and do this. :)

    enhancement v5 
    opened by paulocoghi 1
Owner
Aurélien Delogu
Aurélien Delogu
A minimalist Javascript library to perform AJAX POST and GET Request.

minAjax.js A minimalist Javascript library to perform AJAX POST and GET Request. #Check Pretty Documentation http://flouthoc.github.io/minAjax.js/ #Us

null 6 Apr 27, 2021
Unlocks all brainly answers and bypasses one answer per day limit. Gives infinite free answers & unlocks all textbooks 🔐 ∞

Brainly-LockPick ?? Unlocks all brainly answers and bypasses one answer per day limit. Gives infinite free answers & unlocks textbooks ?? ∞ Note: refr

null 7 Dec 9, 2022
⚖️ Limit an async function's concurrency with ease!

limit-concur Limit an async function's concurrency with ease! Install $ npm i limit-concur Usage import got from 'got' import limitConcur from 'limit-

Tomer Aberbach 19 Apr 8, 2022
Read Medium content without limit!

Medium Unlocker Read Medium content without limit! Aka replacer for Medium Unlimited. Get more information Please visit Wiki page Features Unlock grap

und3fined 303 Dec 24, 2022
A rate-limiter using Durable Objects on CF Workers that actually doesn't rate limit anything.

Rate Limiter - built for Cloudflare Workers, using Durable Objects Features Supports fixed or sliding window algorithms Scoped rate-limiting Caching C

Ian 11 Dec 15, 2022
Standalone AJAX library inspired by jQuery/zepto

ajax Standalone AJAX library inspired by jQuery/zepto Installation component-install ForbesLindesay/ajax Then load using: var ajax = require('ajax');

Forbes Lindesay 365 Dec 17, 2022
A lightweight jQuery Ajax util library.

Just Wait Wait what? The server response. Just Wait is a lightweight jQuery utility that allows you to specify a function to be executed after a speci

Omar Muscatello 2 Jun 22, 2022
Run async code one after another by scheduling promises.

promise-scheduler Run async code in a synchronous order by scheduling promises, with the possibility to cancel pending or active tasks. Optimized for

Matthias 2 Dec 17, 2021
Resolve parallel promises in key-value pairs whilst maintaining type information

async-kv Resolves promises in key-value pairs maintaining type information. Prerequisites NodeJS 12 or later Installation npm i async-kv yarn add asyn

Tony Tamplin 4 Feb 17, 2022
🐶 Learn JS Promises, with your friend 👑 Princess!

?? Learn JS Promises, with your friend ?? Princess!

Baylee Schmeisser 10 Jun 9, 2022
Converts an iterable, iterable of Promises, or async iterable into a Promise of an Array.

iterate-all A utility function that converts any of these: Iterable<T> Iterable<Promise<T>> AsyncIterable<T> AsyncIterable<Promise<T>> Into this: Prom

Lily Scott 8 Jun 7, 2022
A polyfill for ES6-style Promises

ES6-Promise (subset of rsvp.js) This is a polyfill of the ES6 Promise. The implementation is a subset of rsvp.js extracted by @jakearchibald, if you'r

Stefan Penner 7.3k Dec 28, 2022
A Promise-compatible abstraction that defers resolving/rejecting promises to another closure.

Deferred Promise The DeferredPromise class is a Promise-compatible abstraction that defers resolving/rejecting promises to another closure. This class

Open Draft 21 Dec 15, 2022
Remembering promises that were made!

remember-promise A simple utility to remember promises that were made! It is greatly inspired by the p-memoize utility but with additional built-in fe

reda 41 Dec 15, 2022
pjax is a jQuery plugin that uses ajax and pushState to deliver a fast browsing experience with real permalinks, page titles, and a working back button.

pjax = pushState + ajax pjax is a jQuery plugin that uses ajax and pushState to deliver a fast browsing experience with real permalinks, page titles,

Chris Wanstrath 16.8k Jan 5, 2023
we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Understanding DOMs, JQuery, Ajax, Prototypes etc.

JavaScript-for-Complete-Web Development. we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Underst

prasam jain 2 Jul 22, 2022
A jQuery plugin to submit forms with files via AJAX and to get a response with errors.

jquery-ajaxform A jQuery plugin to submit form with files via AJAX and to get a response with errors. Browsers without FormData uses iframe transport

gozoro 2 Mar 30, 2021
Browser library compatible with Node.js request package

Browser Request: The easiest HTTP library you'll ever see Browser Request is a port of Mikeal Rogers's ubiquitous and excellent [request][req] package

Iris Couch 357 Nov 11, 2022
Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript.

You (Might) Don't Need jQuery Frontend environments evolve rapidly nowadays and modern browsers have already implemented a great deal of DOM/BOM APIs

NEFE 20.3k Dec 24, 2022