Skip to content

Instantly share code, notes, and snippets.

@Artazor
Last active July 25, 2023 11:44
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Artazor/8a4b753354ce7376bd23 to your computer and use it in GitHub Desktop.
Save Artazor/8a4b753354ce7376bd23 to your computer and use it in GitHub Desktop.
Await operator desugaring into promises

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).

Asynchronous sequence

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)

Asynchronous selection

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)

Asynchronous repetition

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)

@danielearwicker
Copy link

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 the resolve and reject situations, and reject needs to work like throw. So even a simple await 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:

async function foo1() {
    return "hello";
}

function* foo2() {
    return "hello";
}

foo1 returns a promise created with new Promise(...) and a promise doesn't call listeners synchronously. foo2 returns a generator on which you can immediately (synchronously) call next to get back an object { value: "hello", done: true }.

So a conforming implementation of async, using a conforming implementation of Promise, has some asychronicity burned into it, which makes it unsuitable for fully emulating generators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment