MochiKit.Async - manage asynchronous tasks
var url = "/src/b/bo/bob/MochiKit.Async/META.json"; /* META.json looks something like this: {"name": "MochiKit", "version": "0.5"} */ var d = loadJSONDoc(url); var gotMetadata = function (meta) { if (MochiKit.Async.VERSION == meta.version) { alert("You have the newest MochiKit.Async!"); } else { alert("MochiKit.Async " + meta.version + " is available, upgrade!"); } }; var metadataFetchFailed = function (err) { alert("The metadata for MochiKit.Async could not be fetched :("); }; d.addCallbacks(gotMetadata, metadataFetchFailed);
MochiKit.Async provides facilities to manage asynchronous (as in AJAX [1]) tasks. The model for asynchronous computation used in this module is heavily inspired by Twisted [2].
The current implementation of evalJSONRequest does no input validation. Invalid JSON can execute arbitrary JavaScript code in the client. This isn't normally a concern because of the same-origin policy in web browsers; the server is already sending arbitrary code to the client (your program!).
While this isn't directly relevant to MochiKit, server-side code that produces JSON should consider potential cross-site request forgery. Currently known exploits require a JSON array to be the outer-most object, and the data to be leaked must be known keys in objects contained by that array:
[{"some_known_key": "this can be leaked"}, "but not this"]
This exploit does not apply to the most common usage of JSON, sending an object:
{"some_known_key": "this can't be leaked"}
There are several ways to avoid this, here are a few:
The Deferred constructor encapsulates a single value that is not available yet. The most important example of this in the context of a web browser would be an XMLHttpRequest to a server. The importance of the Deferred is that it allows a consistent API to be exposed for all asynchronous computations that occur exactly once.
The producer of the Deferred is responsible for doing all of the complicated work behind the scenes. This often means waiting for a timer to fire, or waiting for an event (e.g. onreadystatechange of XMLHttpRequest). It could also be coordinating several events (e.g. XMLHttpRequest with a timeout, or several Deferreds (e.g. fetching a set of XML documents that should be processed at the same time).
Since these sorts of tasks do not respond immediately, the producer of the Deferred does the following steps before returning to the consumer:
Since the value is not yet ready, the consumer attaches a function to the Deferred that will be called when the value is ready. This is not unlike setTimeout, or other similar facilities you may already be familiar with. The consumer can also attach an "errback" to the Deferred, which is a callback for error handling.
When the value is ready, the producer simply calls myDeferred.callback(theValue). If an error occurred, it should call myDeferred.errback(theValue) instead. As soon as this happens, the callback that the consumer attached to the Deferred is called with theValue as the only argument.
There are quite a few additional "advanced" features baked into Deferred, such as cancellation and callback chains, so take a look at the API reference if you would like to know more!
Thrown by a Deferred if .callback or .errback are called more than once.
- Availability:
- Available in MochiKit 1.3.1+
Thrown when the JavaScript runtime is not capable of performing the given function. Currently, this happens if the browser does not support XMLHttpRequest.
- Availability:
- Available in MochiKit 1.3.1+
Thrown by a Deferred when it is cancelled, unless a canceller is present and throws something else.
- Availability:
- Available in MochiKit 1.3.1+
Results passed to .fail or .errback of a Deferred are wrapped by this Error if !(result instanceof Error).
- Availability:
- Available in MochiKit 1.3.1+
Thrown when an XMLHttpRequest does not complete successfully for any reason. The req property of the error is the failed XMLHttpRequest object, and for convenience the number property corresponds to req.status.
- Availability:
- Available in MochiKit 1.3.1+
Encapsulates a sequence of callbacks in response to a value that may not yet be available. This is modeled after the Deferred class from Twisted [3].
Why do we want this? JavaScript has no threads, and even if it did, threads are hard. Deferreds are a way of abstracting non-blocking events, such as the final response to an XMLHttpRequest.
The sequence of callbacks is internally represented as a list of 2-tuples containing the callback/errback pair. For example, the following call sequence:
var d = new Deferred(); d.addCallback(myCallback); d.addErrback(myErrback); d.addBoth(myBoth); d.addCallbacks(myCallback, myErrback);is translated into a Deferred with the following internal representation:
[ [myCallback, null], [null, myErrback], [myBoth, myBoth], [myCallback, myErrback] ]The Deferred also keeps track of its current status (fired). Its status may be one of the following three values:
Value Condition -1 no value yet (initial condition) 0 success 1 error A Deferred will be in the error state if one of the following conditions are met:
- The result given to callback or errback is "instanceof Error"
- The callback or errback threw while executing. If the thrown object is not instanceof Error, it will be wrapped with GenericError.
Otherwise, the Deferred will be in the success state. The state of the Deferred determines the next element in the callback sequence to run.
When a callback or errback occurs with the example deferred chain, something equivalent to the following will happen (imagine that exceptions are caught and returned as-is):
// d.callback(result) or d.errback(result) if (!(result instanceof Error)) { result = myCallback(result); } if (result instanceof Error) { result = myErrback(result); } result = myBoth(result); if (result instanceof Error) { result = myErrback(result); } else { result = myCallback(result); }The result is then stored away in case another step is added to the callback sequence. Since the Deferred already has a value available, any new callbacks added will be called immediately.
There are two other "advanced" details about this implementation that are useful:
Callbacks are allowed to return Deferred instances, so you can build complicated sequences of events with (relative) ease.
The creator of the Deferred may specify a canceller. The canceller is a function that will be called if Deferred.prototype.cancel is called before the Deferred fires. You can use this to allow an XMLHttpRequest to be cleanly cancelled, for example. Note that cancel will fire the Deferred with a CancelledError (unless your canceller throws or returns a different Error), so errbacks should be prepared to handle that Error gracefully for cancellable Deferred instances.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.addBoth(func):
Add the same function as both a callback and an errback as the next element on the callback sequence. This is useful for code that you want to guarantee to run, e.g. a finalizer.
If additional arguments are given, then func will be replaced with MochiKit.Base.partial.apply(null, arguments). This differs from Twisted, because the result of the callback or errback will be the last argument passed to func.
If func returns a Deferred, then it will be chained (its value or error will be passed to the next callback). Note that once the returned Deferred is chained, it can no longer accept new callbacks.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.addCallback(func[, ...]):
Add a single callback to the end of the callback sequence.
If additional arguments are given, then func will be replaced with MochiKit.Base.partial.apply(null, arguments). This differs from Twisted, because the result of the callback will be the last argument passed to func.
If func returns a Deferred, then it will be chained (its value or error will be passed to the next callback). Note that once the returned Deferred is chained, it can no longer accept new callbacks.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.addCallbacks(callback, errback):
Add separate callback and errback to the end of the callback sequence. Either callback or errback may be null, but not both.
If callback or errback returns a Deferred, then it will be chained (its value or error will be passed to the next callback). Note that once the returned Deferred is chained, it can no longer accept new callbacks.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.addErrback(func):
Add a single errback to the end of the callback sequence.
If additional arguments are given, then func will be replaced with MochiKit.Base.partial.apply(null, arguments). This differs from Twisted, because the result of the errback will be the last argument passed to func.
If func returns a Deferred, then it will be chained (its value or error will be passed to the next callback). Note that once the returned Deferred is chained, it can no longer accept new callbacks.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.callback([result]):
Begin the callback sequence with a non-Error result. Result may be any value except for a Deferred.
Either .callback or .errback should be called exactly once on a Deferred.
- Availability:
- Available in MochiKit 1.3.1+
Cancels a Deferred that has not yet received a value, or is waiting on another Deferred as its value.
If a canceller is defined, the canceller is called. If the canceller did not return an Error, or there was no canceller, then the errback chain is started with CancelledError.
- Availability:
- Available in MochiKit 1.3.1+
Deferred.prototype.errback([result]):
Begin the callback sequence with an error result. Result may be any value except for a Deferred, but if !(result instanceof Error), it will be wrapped with GenericError.
Either .callback or .errback should be called exactly once on a Deferred.
- Availability:
- Available in MochiKit 1.3.1+
A lock for asynchronous systems.
The locked property of a DeferredLock will be true if it locked, false otherwise. Do not change this property.
- Availability:
- Available in MochiKit 1.3.1+
DeferredLock.prototype.acquire():
Attempt to acquire the lock. Returns a Deferred that fires on lock acquisition with the DeferredLock as the value. If the lock is locked, then the Deferred goes into a waiting list.
- Availability:
- Available in MochiKit 1.3.1+
DeferredLock.prototype.release():
Release the lock. If there is a waiting list, then the first Deferred in that waiting list will be called back.
- Availability:
- Available in MochiKit 1.3.1+
DeferredList(list, [fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller]):
Combine a list of Deferred into one. Track the callbacks and return a list of (success, result) tuples, 'success' being a boolean indicating whether result is a normal result or an error.
Once created, you have access to all Deferred methods, like addCallback, addErrback, addBoth. The behaviour can be changed by the following options:
- fireOnOneCallback:
- Flag for launching the callback once the first Deferred of the list has returned.
- fireOnOneErrback:
- Flag for calling the errback at the first error of a Deferred.
- consumeErrors:
- Flag indicating that any errors raised in the Deferreds should be consumed by the DeferredList.
Example:
// We need to fetch data from 2 different urls var d1 = loadJSONDoc(url1); var d2 = loadJSONDoc(url2); var l1 = new DeferredList([d1, d2], false, false, true); l1.addCallback(function (resultList) { MochiKit.Base.map(function (result) { if (result[0]) { alert("Data is here: " + result[1]); } else { alert("Got an error: " + result[1]); } }, resultList); });
- Availability:
- Available in MochiKit 1.3.1+
callLater(seconds, func[, args...]):
Call func(args...) after at least seconds seconds have elapsed. This is a convenience method for:
func = partial.apply(extend(null, arguments, 1)); return wait(seconds).addCallback(function (res) { return func() });Returns a cancellable Deferred.
- Availability:
- Available in MochiKit 1.3.1+
doXHR(url[, {option: value, ...}]):
Perform a customized XMLHttpRequest and wrap it with a Deferred that may be cancelled.
Note that only 200 (OK), 201 (CREATED), 204 (NO CONTENT) and 304 (NOT MODIFIED) are considered success codes. All other status codes will result in an errback with an XMLHttpRequestError.
- url:
- The URL for this request.
The following options are currently accepted:
- method:
- The HTTP method. Default is 'GET'.
- sendContent:
- The content to send (e.g. with POST). Default is no content.
- queryString:
- If present it will be used to build a query string to append to the url using MochiKit.Base.queryString. Default is no query string.
- username:
- The username for the request. Default is no username.
- password:
- The password for the request. Default is no password.
- headers:
- Additional headers to set in the request, either as an object such as {'Accept': 'text/xml'} or as an Array of 2-Arrays [['Accept', 'text/xml']]. Default is no additional headers.
- mimeType:
- An override mime type. The typical use of this is to pass 'text/xml' to force XMLHttpRequest to attempt to parse responseXML. Default is no override.
- returns:
- Deferred that will callback with the XMLHttpRequest instance on success
- Availability:
- Available in MochiKit 1.4+.
doSimpleXMLHttpRequest(url[, queryArguments...]):
Perform a simple XMLHttpRequest and wrap it with a Deferred that may be cancelled.
Note that only 200 (OK), 201 (CREATED), 204 (NO CONTENT) and 304 (NOT MODIFIED) are considered success codes. All other status codes will result in an errback with an XMLHttpRequestError.
- url:
- The URL to GET
- queryArguments:
If this function is called with more than one argument, a "?" and the result of MochiKit.Base.queryString with the rest of the arguments are appended to the URL.
For example, this will do a GET request to the URL http://example.com?bar=baz:
doSimpleXMLHttpRequest("http://example.com", {bar: "baz"});- returns:
- Deferred that will callback with the XMLHttpRequest instance on success
- Availability:
- Available in MochiKit 1.3.1+. Support for 201 and 204 were added in MochiKit 1.4.
Evaluate a JSON [4] XMLHttpRequest
- req:
- The request whose .responseText property is to be evaluated. If the JSON is wrapped in a comment, the comment will be stripped before evaluation.
- returns:
- A JavaScript object
- Availability:
- Available in MochiKit 1.3.1+
Return a Deferred that has already had .errback(result) called.
See succeed documentation for rationale.
- result:
- The result to give to Deferred.prototype.errback(result).
- returns:
- A new Deferred()
- Availability:
- Available in MochiKit 1.3.1+
A convenience function that returns a DeferredList from the given Array of Deferred instances that will callback with an Array of just results when they're available, or errback on the first array.
- Availability:
- Available in MochiKit 1.3.1+
Return an XMLHttpRequest compliant object for the current platform.
In order of preference:
- new XMLHttpRequest()
- new ActiveXObject('Msxml2.XMLHTTP')
- new ActiveXObject('Microsoft.XMLHTTP')
- new ActiveXObject('Msxml2.XMLHTTP.4.0')
- Availability:
- Available in MochiKit 1.3.1+
maybeDeferred(func[, argument...]):
Call a func with the given arguments and ensure the result is a Deferred.
loadJSONDoc(url[, queryArguments...]):
Do a simple XMLHttpRequest to a URL and get the response as a JSON [4] document.
- url:
- The URL to GET
- queryArguments:
If this function is called with more than one argument, a "?" and the result of MochiKit.Base.queryString with the rest of the arguments are appended to the URL.
For example, this will do a GET request to the URL http://example.com?bar=baz:
loadJSONDoc("http://example.com", {bar: "baz"});- returns:
- Deferred that will callback with the evaluated JSON [4] response upon successful XMLHttpRequest
- Availability:
- Available in MochiKit 1.3.1+
sendXMLHttpRequest(req[, sendContent]):
Set an onreadystatechange handler on an XMLHttpRequest object and send it off. Will return a cancellable Deferred that will callback on success.
Note that only 200 (OK), 201 (CREATED), 204 (NO CONTENT) and 304 (NOT MODIFIED) are considered success codes. All other status codes will result in an errback with an XMLHttpRequestError.
- req:
- An preconfigured XMLHttpRequest object (open has been called).
- sendContent:
- Optional string or DOM content to send over the XMLHttpRequest.
- returns:
- Deferred that will callback with the XMLHttpRequest instance on success.
- Availability:
- Available in MochiKit 1.3.1+. Support for 201 and 204 were added in MochiKit 1.4.
Return a Deferred that has already had .callback(result) called.
This is useful when you're writing synchronous code to an asynchronous interface: i.e., some code is calling you expecting a Deferred result, but you don't actually need to do anything asynchronous. Just return succeed(theResult).
See fail for a version of this function that uses a failing Deferred rather than a successful one.
- result:
- The result to give to Deferred.prototype.callback(result)
- returns:
- a new Deferred
- Availability:
- Available in MochiKit 1.3.1+
Return a new cancellable Deferred that will .callback(res) after at least seconds seconds have elapsed.
- Availability:
- Available in MochiKit 1.3.1+
[1] | AJAX, Asynchronous JavaScript and XML: http://en.wikipedia.org/wiki/AJAX |
[2] | Twisted, an event-driven networking framework written in Python: http://twistedmatrix.com/ |
[3] | Twisted Deferred Reference: http://twistedmatrix.com/projects/core/documentation/howto/defer.html |
[4] | (1, 2, 3) JSON, JavaScript Object Notation: http://json.org/ |
Copyright 2005 Bob Ippolito <bob@redivi.com>. This program is dual-licensed free software; you can redistribute it and/or modify it under the terms of the MIT License or the Academic Free License v2.1.