The introduction of promises and generators in ECMAScript presents an opportunity to dramatically improve the language-level model for writing asynchronous code in ECMAScript.
A similar proposal was made with Deferred Functions during ES6 discussions. The proposal here supports the same use cases, using similar or the same syntax, but directly building upon control flow structures parallel to those of generators, and using promises for the return type, instead of defining custom mechanisms.
Development of this proposal is happening at https://github.com/tc39/ecmascript-asyncawait. Please file issues there. Non-trivial contributions are limited to TC39 members but pull requests for minor issues are welcome and encouraged!
This proposal was accepted into Stage 3 ("Candidate") of the ECMAScript spec process in September 2015. The champion intends for this proposal to be accepted into Stage 4 ("Finished") by the end of November.
Take the following example, first written using Promises. This code chains a set of animations on an element, stopping when there is an exception in an animation, and returning the value produced by the final succesfully executed animation.
function chainAnimationsPromise(elem, animations) {
let ret = null;
let p = currentPromise;
for(const anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
})
}
return p.catch(function(e) {
/* ignore and keep going */
}).then(function() {
return ret;
});
}
Already with promises, the code is much improved from a straight callback style, where this sort of looping and exception handling is challenging.
Task.js and similar libraries offer a way to use generators to further simplify the code maintaining the same meaning:
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(const anim of animations) {
ret = yield anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
});
}
This is a marked improvement. All of the promise boilerplate above and beyond the semantic content of the code is removed, and the body of the inner function represents user intent. However, there is an outer layer of boilerplate to wrap the code in an additional generator function and pass it to a library to convert to a promise. This layer needs to be repeated in every function that uses this mechanism to produce a promise. This is so common in typical async Javascript code, that there is value in removing the need for the remaining boilerplate.
With async functions, all the remaining boilerplate is removed, leaving only the semantically meaningful code in the program text:
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(const anim of animations) {
ret = await anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
}
yield
is await
is always parsed as an yield
is always allowed as an identifier of an When processing the production
eval
or the arguments
.
"*default*"
».
*default*
" is used within this specification as a synthetic name for hoistable anonymous functions that are defined using export declarations.With parameter symbol.
With parameter scope.
"default"
).With parameters functionObject and
With parameter symbol.
new.target
, this
, and super
usage within an AsyncArrowFunction.With parameters iteratorRecord and environment.
With parameter functionObject and
With parameter symbol.
With parameters object and enumerable.
The abstract operation AsyncFunctionCreate requires the arguments: kind which is one of (
"non-constructor"
).Function F is called with the parameter value.
Function F is called with the parameter reason.
The AsyncFunction
Constructor is the %AsyncFunction% intrinsic object and is a subclass of Function
. When AsyncFunction
is called as a function rather than as a constructor, it creates and initializes a new AsyncFunction object. Thus the function call AsyncFunction(…)
is equivalent to the object creation expression new AsyncFunction(…)
with the same arguments.
The AsyncFunction
constructor is designed to be subclassable. It may be used as the value of an extends clause of a class definition. Subclass constructors that intend to inherit the specified AsyncFunction behaviour must include a super call to the AsyncFunction constructor to create and initialize a subclass instances with the internal slots necessary for built-in async function behaviour.
The last argument specifies the body (executable code) of an async function. Any preceding arguments specify formal parameters.
When the AsyncFunction
function is called with some arguments p1, p2, ..., pn, body (where n might be 0, that is, there are no p arguments, and where body might also not be provided), the following steps are taken:
The AsyncFunction constructor is a standard built-in function object that inherits from the Function
constructor. The value of the [[Prototype]] internal slot of the AsyncFunction constructor is the intrinsic object %Function%.
The value of the [[Extensible]] internal slot of the AsyncFunction constructor is
The value of the
The AsyncFunction constructor has the following properties:
This is a data property with a value of 1. This property has the attributes { [[Writable]]:
The intitial value of AsyncFunction.prototype
is the intrinsic object %AsyncFunctionPrototype%.
This property has the attributes { [[Writable]]:
The AsyncFunction prototype object is an ordinary object. In addition to being the value of the prototype property of the %AsyncFunction% intrinsic, it is the %AsyncFunctionPrototype% intrinsic.
The value of the [[Prototype]] internal slot of the AsyncFunction prototype object is the %FunctionPrototype% intrinsic object. The initial value of the [[Extensible]] internal slot of the AsyncFunction prototype object is
The AsyncFunction prototype object does not have a prototype property.
The initial value of AsyncFunction.prototype.constructor
is the intrinsic object %AsyncFunction%
This property has the attributes { [[Writable]]:
The initial value of the @@toStringTag property is the string value "AsyncFunction".
This property has the attributes { [[Writable]]:
Every AsyncFunction instance is an ECMAScript function object and has the internal slots listed in Table 27. The value of the [[FunctionKind]] internal slot for all such instances is "normal"
. AsyncFunction instances are not constructors and do not have a [[Construct]] internal slot. AsyncFunction instances do not have a prototype property as they are not constructable.
Each AsyncFunction instance has the following own properties:
The value of the
This property has the attributes { [[Writable]]:
The specification for the name
property of Function instances given in 19.2.4.2 also applies to AsyncFunction instances.
When the abstract operation
With parameters functionObject and
With parameters functionObject and
With parameters functionObject and
Unless otherwise defined in this section, every production that includes a Yield
parameter is modified to include an Await
parameter and any Non-terminal with a ?Yield
parameter is modified to include an ?Await
parameter.
"yield"
.
"await"
.
?Yield
parameter for
This list is not complete. It also does not duplicate any information on the Github issues list. It serves as a reminder to the author only.
async function <name>?<argumentlist><body>
=>
function <name>?<argumentlist>{ return spawn(function*() <body>, this); }
The spawn
used in the above desugaring is a call to the following algorithm. This algorithm does not need to be exposed directly as an API to user code, it is part of the semantics of async functions.
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
© 2016 Microsoft Inc., Ecma International
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT http://www.ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.