This page is a revision of deferred_functions to explain how to express it as a library in terms of the concurrency strawman and the generators proposal, and as an enhancement to the Q API from the concurrency strawman.

Async Functions

Async functions ease the burden of asynchronous programming.

Asynchronous Programming

Ecmascript programming environments typically are single threaded and pausing execution for long periods is undesirable. ES host environments use callbacks for operations which may take a long time like network IO or system timers.

For Example:

  function animate(element, callback) {
    var i = -1;
    function loop() {
      i++;
      if (i < 100) {
        element.style.left = i;
        window.setTimeout(loop, 20);
      } else {
        callback();
      }
    }
    loop();
  };
  animate(document.getElementById('box'), 
          function() { alert('Done!'); });

The concurrency strawman provides a Q API for defining and manipulating promises, that avoid the inversion of control necessitated by such callback-oriented programming.

const delay(millis, answer = undefined) {
    const deferredResult = Q.defer();
    setTimeout(const() { deferredResult.resolve(answer); }, millis);
    return deferredResult.promise;
  }
 
  function asyncAnimate(element) {
    var i = -1;
    var deferred = Q.defer();
    function loop() {
      i++;
      if (i < 100) {
        element.style.left = i;
        Q(delay(20)).then(loop);
      } else {
        deferred.resolve();
      }
    }
    loop();
    return deferred.promise;
  };
  Q(asyncAnimate(document.getElementById('box'))).then(
    function() { alert('Done!'); });

Programming with promises improves composability of callback patterns, but there are still drawbacks in programming in this style. The completion of the computation must be enclosed in a callback function passed to the ‘Q(p).then’ function.

Authoring the callback function has proved difficult for some ES programmers. Control flow constructs (if, while, for, try) do not compose across function boundaries. The programmer must manually twist the control flow into continuation passing style. The callback function does not by default have the same ‘this’ binding as the enclosing function which is a frequent source of programmer error.

Async Functions

Async functions allow asynchronous code to be written using existing control flow constructs. They are expressed by composing the Q.async method proposed by this strawman with generators.

  const asyncAnimate = Q.async(function*(element) {
    for (var i = 0; i < 100; ++i) {
      element.style.left = i;
      yield delay(20);
    }
  });
  Q(asyncAnimate(document.getElementById('box'))).then(
    function() { alert('Done!'); });

This strawman adds the Q.async method to the Q API. When Q.async is called with a generator function, it returns an async function. A yield expression within the body of the generator function argument is an await expression.

The await expression evaluates the expression after the yield. The result of evaluating the expression in an await expression is a promised value. The promised value is either a promise or a normal value. After computing the promised value the await expression suspends execution of the current function, attaches the continuation of the current function to the promised value by calling the Q(p).then method, and then returns what that Q(p).then returns.

Q(p).then queues callbacks to be called later, in a separate turn, once the promised value is resolved. If the promised value is either not a promise or is a resolved promise, then Q(p).then queues calls to these callbacks in the general event queue – the same one used by setTimeout, etc. If the promised value is an unresolved promise, Q(p).then queues the callback within the promise, to be requeued once the promise is resolved. Q(p).then immediately returns a promise for what the queued callback will eventually return.

The value of the await expression as a whole is thus the resolution of the promised value, i.e., the value that was promised. And the return value of a call to an async function is thus a promise for the value that the completion of the function will eventually return. Callbacks to Q(p).then on that promise will eventually be called with the value that the completion of the async function eventually does return.

Returning Values From Async Functions

When the completion of an async function completes by returning a value, the returned value is passed as the argument when invoking callbacks registered on the promise returned by the returned function. The value of an await expression is the value of the argument passed to the callback registered on the promised value.

  function asyncXHR(url) {
    var deferred = Q.defer();
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.send(null);
    request.onreadystatechange = function() {
      if (request.readyState == 4) {
        // call all registered callbacks with 'request' as argument
        deferred.resolve(request);
      }
    };
    return deferred.promise;
  }
 
  const asyncLoadRedirectUrl = Q.async(function*(redirectUrl) {
    // redirectUrl contains another url
    var urlXHR = yield asyncXHR(redirectUrl);
    var url = urlXHR.responseText;
 
    var valueXHR = yield asyncXHR(url);
    // call all registered callbacks with 'valueXHR.responseText' as argument
    return valueXHR.responseText;
  }):
 
  // alert the value of the redirected url
  Q(asyncLoadRedirectUrl('http://lolcatz.com/redirect')).then(
    function(value) { alert(value); });

Throwing From Async Functions

Q(p).then takes two arguments. The p argument is the promised value (promise or regular value), whose resolution we’re interested in. The first argument is the callback to be invoked if the promise becomes fulfilled with a value. The second is the errback to be invoked if the promise becomes broken. When an async function completes by throwing an exception, its returned promise becomes broken with the thrown exception as the reason, and so the registered errbacks are invoked with the thrown exception as the argument.

  // alert the value of the redirected url
  Q(asyncLoadRedirectUrl('http://lolcatz.com/redirect')).then(
    function(value) { alert('Success: ' + value); },
    function(err) { alert('Failure: ' + err); });

Similarly, when an awaited on expression completes with an error - ie. its errback is invoked - the result of the await expression is to throw the error value.

Reference Implementation

  Q.async = function(generatorFunc) {
 
    return function asyncFunc(...args) {
      const generator = generatorFunc.apply(this, args);
      const callback = continuer.bind(void 0, 'send');
      const errback = continuer.bind(void 0, 'throw');
 
      function continuer(verb, valueOrErr) {
        let promisedValue;
        try {
          promisedValue = generator[verb](valueOrErr);
        } catch (err) {
          if (isStopIteration(err)) { return Q(err.value); }
          return Q.reject(err);
        }
        return Q(promisedValue).then(callback, errback);
      }
 
      return callback(void 0);
    };
  };

See

concurrency

generators

deferred_functions

Tom's prototype using Firefox’s current non-standard generators and Kris Kowal's qcomm implementation of Q.

 
strawman/async_functions.txt · Last modified: 2013/04/23 12:07 by markm
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki