85

I was just reading this fantastic article «Generators» and it clearly highlights this function, which is a helper function for handling generator functions:

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

which I hypothesize is more or less the way the async keyword is implemented with async/await. So the question is, if that is the case, then what the heck is the difference between the await keyword and the yield keyword? Does await always turn something into a promise, whereas yield makes no such guarantee? That is my best guess!

You can also see how async/await is similar to yield with generators in this article where he describes the 'spawn' function ES7 async functions.

Mike B.
  • 10,955
  • 19
  • 76
  • 118
Alexander Mills
  • 1
  • 80
  • 344
  • 642
  • 1
    async function -> a coroutine. generator -> iterator which uses a coroutine to manage its inner iterations mechanism. await suspends a coroutine, while yield return a result from a coroutine which some generator uses – David Haim Mar 24 '16 at 09:24
  • 1
    `async/await` is not part of ES7. Please read tag description. – Felix Kling Mar 24 '16 at 13:53
  • @david haim, yeah but async await is built on top of generators so they are not distinct – Alexander Mills Mar 24 '16 at 18:14

6 Answers6

47

Well, it turns out that there is a very close relationship between async/await and generators. And I believe async/await will always be built on generators. If you look at the way Babel transpiles async/await:

Babel takes this:

this.it('is a test', async function () {

    const foo = await 3;
    const bar = await new Promise(resolve => resolve('7'));
    const baz = bar * foo;
    console.log(baz);

});

and turns it into this

function _asyncToGenerator(fn) {
    return function () {
        var gen = fn.apply(this, arguments);
        return new Promise(function (resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function (value) {
                        return step("next", value);
                    }, function (err) {
                        return step("throw", err);
                    });
                }
            }

            return step("next");
        });
    };
}


this.it('is a test', _asyncToGenerator(function* () {   // << now it's a generator

    const foo = yield 3;    //  <<< now it's yield, not await
    const bar = yield new Promise(resolve => resolve(7));
    const baz = bar * foo;
    console.log(baz);

}));

you do the math.

This makes it look like the async keyword is just that wrapper function, but if that's the case then await just gets turned into yield, there will probably be a bit more to the picture later on when they become native.

You can see more of an explanation for this here: https://www.promisejs.org/generators/

Mike B.
  • 10,955
  • 19
  • 76
  • 118
Alexander Mills
  • 1
  • 80
  • 344
  • 642
  • 3
    NodeJS has native async/await for a while now, without generators: https://codeforgeek.com/2017/02/asyncawait-function-officially-shipped-nodejs-7-6-0/ – Bram Apr 08 '18 at 18:03
  • 3
    @Bram native implementation absolutely uses generators under the hood, same thing, just abstracted away. – Alexander Mills Apr 29 '18 at 19:58
  • 4
    I don't think so. Async/await is natively implemented in the V8 engine. Generators where a ES6 feature, async/await is ES7. It was part of the 5.5 release of the V8 engine (which is used in Node): https://v8project.blogspot.nl/2016/10/v8-release-55.html. It is possible to transpile ES7 async/await into ES6 generators, but with new versions of NodeJS this is no longer needed, and the performance of async/await even seems to be better then generators: https://medium.com/@markherhold/generators-vs-async-await-performance-806d8375a01a – Bram May 01 '18 at 11:02
  • 1
    async/await uses generators to do its thing – Alexander Mills Jul 12 '18 at 18:45
  • 2
    @AlexanderMills can you please share some legit resources which says async/await uses generators internally? check this ans stackoverflow.com/a/39384160/3933557 which contradicts this argument. I think , just because Babel uses generators, it does not mean it is implemented similarly under the hood. Any thoughts on this – Samarendra Oct 13 '20 at 11:22
45

yield can be considered to be the building block of await. yield takes the value it's given and passes it to the caller. The caller can then do whatever it wishes with that value (1). Later the caller may give a value back to the generator (via generator.next()) which becomes the result of the yield expression (2), or an error that will appear to be thrown by the yield expression (3).

async-await can be considered to use yield. At (1) the caller (i.e. the async-await driver - similar to the function you posted) will wrap the value in a promise using a similar algorithm to new Promise(r => r(value) (note, not Promise.resolve, but that's not a big deal). It then waits for the promise to resolve. If it fulfills, it passes the fulfilled value back at (2). If it rejects, it throws the rejection reason as an error at (3).

So the utility of async-await is this machinery that uses yield to unwrap the yielded value as a promise and pass its resolved value back, repeating until the function returns its final value.

Arnavion
  • 2,721
  • 21
  • 26
  • 2
    check this answer https://stackoverflow.com/a/39384160/3933557 which contradicts this argument. async-await looks similar to yield but it uses promises chain under the hood. Please share if you have any good resource says "async-await can be considered to use yield". – Samarendra Oct 13 '20 at 11:27
  • 1
    I'm not sure how you're taking that answer to be "contradicting this argument", because it's saying the same thing as this answer. >In the meantime, transpilers like Babel allow you to write async/await and convert the code to generators. – Arnavion Oct 13 '20 at 21:15
  • 1
    its says babel convert to generators but what you are saying is "yield can be considered to be the building block of await" and "async-await can be considered to use yield.". which is not correct to my understanding (subject to correction). async-await internally uses promise chains as mentioned in that answer. i want to understand if there is something i am missing, can you please share your thoughts on this. – Samarendra Oct 14 '20 at 07:14
  • This answer does not make the claim that all ES engines in the whole world internally implement promises using generators. Some may; some may not; it is irrelevant to the question that this is an answer to. Nevertheless, the way promises work can be understood using generators with a particular way to drive the generator, and that is what this answer explains. – Arnavion Oct 14 '20 at 19:40
29

what the heck is the difference between the await keyword and the yield keyword?

The await keyword is only to be used in async functions, while the yield keyword is only to be used in generator function*s. And those are obviously different as well - the one returns promises, the other returns generators.

Does await always turn something into a promise, whereas yield makes no such guarantee?

Yes, await will call Promise.resolve on the awaited value.

yield just yields the value outside of the generator.

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • A minor nit, but as I mentioned in my answer the spec doesn't use Promise.resolve (it used to earlier), it uses PromiseCapability::resolve which is more accurately represented by the Promise constructor. – Arnavion Mar 24 '16 at 11:29
  • @Arnavion: [`Promise.resolve`](http://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve) uses exactly the same `new PromiseCapability(%Promise%)` that the async/await spec uses directly, I just thought `Promise.resolve` is better to understand. – Bergi Mar 24 '16 at 11:40
  • 1
    `Promise.resolve` has an extra "IsPromise == true? then return same value" short-circuit that async does not have. That is, `await p` where `p` is a promise will return a new promise that resolves to `p`, whereas `Promise.resolve(p)` would return `p`. – Arnavion Mar 24 '16 at 20:42
  • Oh I missed that - I thought this was only in `Promise.cast` and was deprecated for consistency reasons. But it doesn't matter, we don't really see that promise anyway. – Bergi Mar 24 '16 at 22:56
  • 2
    `var r = await p; console.log(r);` should be transformed to something like: `p.then(console.log);`, while `p` might be created as: `var p = new Promise(resolve => setTimeout(resolve, 1000, 42));`, so it is wrong to say "await _calls_ Promise.resolve", it is some other code totally far away from the 'await' expression that invokes `Promise.resolve`, so the transformed `await` expression, i.e. `Promise.then(console.log)` would be invoked and print out `42`. – Dejavu Jan 12 '18 at 06:49
17

tl;dr

Use async/await 99% of the time over generators. Why?

  1. async/await directly replaces the most common workflow of promise chains allowing code to be declared as if it was synchronous, dramatically simplifying it.

  2. Generators abstract the use case where you would call a series of async-operations that depend on each other and eventually will be in a "done" state. The most simple example would be paging through results that eventually return the last set but you would only call a page as needed, not immediately in succession.

  3. async/await is actually an abstraction built on top of generators to make working with promises easier.

See very in-depth Explanation of Async/Await vs. Generators

Community
  • 1
  • 1
King Friday
  • 19,950
  • 9
  • 78
  • 78
5

Try this test programs which I used to understand await/async with promises.

Program #1: without promises it doesn't run in sequence

function functionA() {
    console.log('functionA called');
    setTimeout(function() {
        console.log('functionA timeout called');
        return 10;
    }, 15000);

}

function functionB(valueA) {
    console.log('functionB called');
    setTimeout(function() {
        console.log('functionB timeout called = ' + valueA);
        return 20 + valueA;
    }, 10000);
}

function functionC(valueA, valueB) {

    console.log('functionC called');
    setTimeout(function() {
        console.log('functionC timeout called = ' + valueA);
        return valueA + valueB;
    }, 10000);

}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

Program #2: with promises

function functionA() {
    return new Promise((resolve, reject) => {
        console.log('functionA called');
        setTimeout(function() {
            console.log('functionA timeout called');
            // return 10;
            return resolve(10);
        }, 15000);
    });   
}

function functionB(valueA) {
    return new Promise((resolve, reject) => {
        console.log('functionB called');
        setTimeout(function() {
            console.log('functionB timeout called = ' + valueA);
            return resolve(20 + valueA);
        }, 10000);

    });
}

function functionC(valueA, valueB) {
    return new Promise((resolve, reject) => {
        console.log('functionC called');
        setTimeout(function() {
            console.log('functionC timeout called = ' + valueA);
            return resolve(valueA + valueB);
        }, 10000);

    });
}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');
Mike B.
  • 10,955
  • 19
  • 76
  • 118
Kamal Kumar
  • 2,495
  • 1
  • 16
  • 15
0

In many ways, generators are a superset of async/await. Right now async/await has cleaner stack traces than co, the most popular async/await-like generator based lib. You can implement your own flavor of async/await using generators and add new features, like built-in support for yield on non-promises or building it on RxJS observables.

So, in short, generators give you more flexibility and generator-based libs generally have more features. But async/await is a core part of the language, it's standardized and won't change under you, and you don't need a library to use it. I have a blog post with more details on the difference between async/await and generators.

vkarpov15
  • 2,465
  • 18
  • 17