Here is an example how await
operator can be desugared into Promise-based code without ES6 generators (and state machines that emulate generators missing in ES5).
The only assumption is that there is Promise.resolve
(to create an empty promise for the loop finalization) among the globals.
Examples are written with "throw early" policy adoption: if promisified code has a synchronous preamble that throws we prefer to throw instead of creating rejected Promise.
Note that patterns below don't cover control flow breaks like return
, break
, continue
and throw
(for these breaks we should use a bit more complex structures).
function() {
var b, e;
a();
b = await c;
d();
e = (await f[3].f()).x.y.z;
g();
return h;
}
when desugared into Promises becomes
function() {
var b, e;
a();
return c.then(function(_ref){
b = _ref;
d();
return f[3].f().then(function(_ref){
e = _ref.x.y.z;
g();
return h;
});
});
}
Note, that we can reformat the code to make it line-to-line prefect:
![Sequence line-to line comparison] (https://s3.amazonaws.com/f.cl.ly/items/470K1D3c382b0c2O3G3b/Screen%20Shot%202015-02-12%20at%2013.26.01.png)
function() {
var d, n;
a()
if (x < y) {
b();
d = await e()
f()
} else {
m();
n = await p()
q();
}
r();
return x;
}
when desugared to promises becomes:
function() {
var d, n;
a();
function _if() { if (x < y) {
b();
return e().then(function(_ref){
d = ref;
f();
});
} else {
m()
return p().then(function(_ref){
n = _ref;
q();
});
}
return _if().then(function(){
r();
return x;
});
}
Line-to-line: ![Selection line-to-line comparison] (https://s3.amazonaws.com/f.cl.ly/items/122t2M1Z2m0U2V3E3k1G/Screen%20Shot%202015-02-12%20at%2013.30.08.png)
function() {
var i, c;
a();
i = 0;
while (i < 10) {
b();
c = await d()
e()
i++;
}
f();
return g;
}
when desugared to promises becomes
// Repetition
function() {
var i, c;
a();
i = 0;
function _while() {
if(i < 10) {
b();
return d().then(function(_ref){
c = _ref;
e();
i++;
return _while();
});
} else {
return Promise.resolve();
}
}
return _while().then(function(){
f();
return g;
});
}
Line-to-line comparison: ![Repetition line-to-line comparison] (https://s3.amazonaws.com/f.cl.ly/items/0b3k2h252F0U2h2i233k/Screen%20Shot%202015-02-12%20at%2013.38.14.png)
As well as the complicating factors you noted (continue, break, throw, etc) you'd need to bear in mind that every time you enlist on
then
, you need to handle both theresolve
andreject
situations, andreject
needs to work likethrow
. So even a simpleawait
would end up looking pretty ugly.One reason it is more popular to do this transformation via generators is because most preprocessors or compilers will need to support generators as well (given that they are a standard ES6 feature). By supporting generators,
async
becomes easy.The reverse is not entirely possible. A trivial pair of examples that don't even use
await
/yield
:foo1
returns a promise created withnew Promise(...)
and a promise doesn't call listeners synchronously.foo2
returns a generator on which you can immediately (synchronously) callnext
to get back an object{ value: "hello", done: true }
.So a conforming implementation of
async
, using a conforming implementation ofPromise
, has some asychronicity burned into it, which makes it unsuitable for fully emulating generators.