bluejax
v1.1.0
Published
jQuery AJAX wrapped in Bluebird promises.
Downloads
19
Readme
Bluejax is a library that wraps jQuery's
ajax
function in Bluebird
promises. This is not the
first library of this kind, but the other libraries that existed when Bluejax
was created did not satisfy our needs, or appeared defunct.
Features
Wraps
jQuery.ajax
in Bluebird promises.Optionally retry failed queries a number of times before giving up.
Optionally diagnoses failed queries: network failure, server down, something else?
jQuery Versions Supported
In the 3.x and 2.x series: any version.
In the 1.x series: 1.11 or later. This being said, Bluejax probably works fine with 1.9 and 1.10.
Platforms Supported
Bluejax's is tested on Chrome, Firefox, IE11 and 10, Edge, Opera, and Safari (on El Capitan, Yosemite, Mavericks). We test against the latest versions offered by the vendors of these browsers on their respective platforms.
Bluejax's suite fails on IE9. I currently have no plans to work on adding support for IE9 but if you want to provide a pull request that will make Bluejax run on IE9 and will keep its current tests passing, you are welcome to do so.
Loading Bluejax
AMD
Your loader should be configured so that it can find Bluejax, jQuery
and Bluebird. jQuery and Bluebird are requested by Bluejax as
jquery
and bluebird
respectively.
CommonJS
Once installed, you should just be able to do var bluejax =
require("bluejax")
. jQuery and Bluebird should also be installed. Just like in
the AMD case, they are required as jquery
and bluebird
respectively.
script
elements
If you are just loading it in a browser with script
. jQuery and
Bluebird must have been loaded beforehand.
Using Bluejax
The module exports these items:
ajax(...)
is a function that passes all its arguments tojQuery.ajax
. By default, it returns a promise that resolves to the data received. If it fails, it will reject the promise with aGeneralAjaxError
or a an exception whose class is derived fromGeneralAjaxError
.ajax(url).then(function (data) { document.getElementById("foo").innerHTML = data; });
ajax$(...)
does the same thing asajax(...)
but it returns an object that has the keysxhr
andpromise
. The value ofpromise
is the same as the value returned byajax(...)
thexhr
object is an object like thejqXHR
returned byjQuery.ajax
.Important note: the
xhr
object only encapsulate the tries that Bluejax performs. It will be successful if the Ajax query was successful within the number of tries specified and will fail if the tries failed. It is not affected by the diagnosis done after all tries failed. If you need diagnostic information you must use the promise.This call can be useful for the purpose of inserting Bluejax in contexts that expect to work with
jQuery.ajax
. For instance, I can make Backbone use Bluejax for all its Ajax requests. In this case, I use a specialuseBluejax
option to turn on the use of Bluejax. Those calls that do use useuseBluejax
go through unchanged.var origAjax = Backbone.ajax; Backbone.ajax = function ajaxWrapper(options) { if (!options.useBluejax) { return origAjax.call(this, options); } // Return the xhr because this is what callers to ``Backbone.ajax`` // expect. return ajax$(options).xhr; };
Although the callers of
Backbone.ajax
do not get a promise. The callers still benefit from the possibility of retrying queries. If everything goes to hell they do not get the diagnosis information themselves but a handler that listens for unhandled rejections could use the rejection information to provide an informative error message. This is actually the case for my Backbone application that uses the snippet above.make(options, field)
is a utility function that creates a newajax$
-like function. Theoptions
parameter is an object containing Bluejax options. The returned value is a new function that works likeajax
but which has its Bluejax options set to the values contained in theoptions
object.The
field
parameter allows you to automatically extract a field. If you do not specify a value forfield
, then the return value will be the same object returned by$ajax
. If you want to retrieve only one field from that object, you must specify the field name. To get the same value as theajax(...)
call, you'd need to put"promise"
for the value offield
.Example: it is possible to create a new function that will return verbose results:
var ajaxVerbose = make({verboseResults: true}, "promise");
and use it in the same wayajax
is used:ajaxVerbose("http://example.com").spread(function (data, textStatus, jqXHR) { ... });
GeneralAjaxError
is a class that derives from JavaScript's stockError
. It aims to provide a somewhat saner way to handle Ajax errors than what jQuery provides by default. WhenjQuery.ajax
fails, Bluejax creates an exception derived fromGeneralAjaxError
that has itsjqXHR
,textStatus
anderrorThrown
fields set to the corresponding fields of callback that should be passed to the.fail(...)
method of the object returned byjQuery.ajax
. Itsmessage
field is constructed as follows:If
errorThrown
is set, the message is "Ajax operation failed:errorThrown
(jqXHR.status
).".Otherwise, if
textStatus
is set, the message is "Ajax operation failed:textStatus
.".Otherwise, the message is "Ajax operation failed."
HttpError
is raised if the response had an HTTP status that signaled an error.TimeoutError
is raised if the rejection was caused by a timeout.AbortError
is raised if the rejection was caused by an abort.ParserError
is raised if the rejection was caused by a parsing problem.ConnectivityError
indicates a network problem. This class of error is never raised directly but is raised through its children:BrowserOfflineError
is raised if the browser is offline.ServerDownError
is raised if the server is down.NetworkDownError
is raised if the network is down.
If none of the more specialized cases above apply, then
AjaxError
is raised.
Options
Bluejax currently supports these options:
tries
tells Bluejax to retry the query for a number of times. Note that the value here should be a number greater than 1. (Values less than 1 yield undefined behavior.)shouldRetry
is a function with the following signatureshouldRetry(jqXHR, textStatus, errorThrown)
. It should returntrue
if the query should be retried, orfalse
if an error should be returned immediately.If no value is specified, the default function returns
true
if the previous query failed due to reasons other than the HTTP status code reporting an error, aborted or had a parser error. Basically, it retries the connection if the issue appears to be at the network level rather than an application issue.delay
specifies the delay between retries, in milliseconds.diagnose
is an object with the following keys:on
must betrue
for diagnosis to happen. (This makes diagnosis easy to turn off, while keeping the other diagnosis settings intact.) Make sure to read the section on diagnosis rules and URL transformations below before to understand what it is that happens when you turn diagnosis on!!!serverURL
must be a URL that used to test whether your server is running or not. We recommend making it a path that is inexpensive to serve. For instance, your internet-facing nginx instance could have a rule that serves 200 and no contents for GETs to/ping
. Bluejax uses this URL to double check whether your server is up.knownServers
must be an array of URLs to known internet servers. Bluejax uses these URLs to determine whether the Internet is accessible or not.
verboseExceptions
when set totrue
will cause exception messages to additionally contain the text "Called with: " followed by a JSON dump of the options that were passed toajax(...)
. This can be useful to identify which call precisely is failing.verboseResults
causes the promise to resolve to[data, textStatus, jqXHR]
where each element of the array is the corresponding parameter passed to the callback of the.done(...)
method of the object returned byjQuery.ajax
. You would use this in a case where just receivingdata
is not enough for your usage scenario.Bluebird's
.spread
method is useful to unpack the array:ajax(url, { bluejaxOptions: { verboseResults: true } }).spread(function (data, textStatus, jqXHR) {...
There are two ways to set Bluejax options:
You can set set the
bluejaxOptions
field on a settings object passed toajax
. Remember thatajax(...)
takes the same parameters asjQuery.ajax
. When you pass asettings
parameter to the call, it may contain abluejaxOptions
field that setsverboseExceptions
:bluejax.ajax({ url: "http://example.com", bluejaxOptions: { verboseExceptions: true } });
You can create a new
ajax
-like function withmake
.
Diagnosis Rules
Diagnosis happens only if the final try for the request failed with an error other than an HTTP error, an abort or a parser error. Otherwise, no diagnosis occurs and the error is reported immediately.
Bluejax uses the following rules when diagnosis is requested:
If
navigator.onLine
is false, Bluejax reports that the browser is offline.If a
serverURL
is specified, then it checks whether the server is responds to a GET at this URL:
A. If the server responds, Bluejax reports the error that was reported by the last try.
B. If the server does not respond, Bluejax reports the result of a connectivity check.
- If a
serverURL
was not specified, Bluejax reports the result of a connectivity check.
Connectivity Check
If
knownServers
does not exist or is an empty list, then it reports that the server appears to be down.Otherwise, Bluejax contacts all the servers. If none of them respond, then it reports that the network appears to be down. Otherwise, it reports that the server appears to be down.
URL Checking Rules
For all URLs used in diagnosis, these two transformations are applied in order:
If the URL ends with a
/
and has no query string, then the URL is transformed by addingfavicon.ico
. Requesting the root page of a site may result in a large amount of data being returned. Requestingfavicon.ico
would in most cases result in a relatively small amount of data.If the URL has no query, then the URL is transformed by adding a query that is a single number corresponding to the current time. This is done to bust caches.
So if you specify a known server as http://www.google.com/
the URL used for
the query will be http://www.google.com/favicon.icon?ttttt
, where ttttt
is the number described above. If you specify http://www.example.com/foo
then the URL used for the query will be http://www.example.com/foo?ttttt
. If
the URL you give in the options contains a query, it won't be modified at
all. (We do not recommend such case.)
Developing Bluejax
If you produce a pull request run gulp lint
and gulp test
first to make
sure they run clean. If you add features, do add tests.
Coverage
We need a Mocha run to test loading Bluejax as a CommonJS module with script
elements. The Karma run, which exercises over 95% of the code, uses RequireJS
to load Bluejax.
Ideally, we combine the results of the Karma runs with the result of the Mocha
run. The problem though is that as we speak, karma-coverage
uses Istanbul
0.4.x but to get coverage with Mocha with code that has run through Babel, we
need Istanbul 1.0.0-alpha2 or higher. We've not been able to combine the formats
produced by the various versions.
Testing
Bluejax is tested using BrowserStack. BrowserStack provides this service for free under their program for supporting open-source software.