0

The following implements a control flow wrapper co enabling asynchronous code to be delineated only by the yield keyword.

Is this basically what async/await does under the hood in ESwhatever?

co(function*() {
    console.log('...');
    yield one();
    console.log('...');
    yield two();
})


function co(gFn) {
    var g = gFn();

    return Promise.resolve()
        .then(go);

    function go() {        
        var result = g.next();
        if(result.done) {
            return;
        }
        if(isPromise(result.value)) {
            return result.value.then(go); // Promises block until resolution.
        }
        return Promise.resolve(result);
    }    
}

function isPromise(o) {
    return o instanceof Promise;
}

function one() {
    return new Promise(resolve => setTimeout(() => (console.log('one'), resolve()), 1000));
}

function two() {
    return new Promise(resolve => setTimeout(() => (console.log('two'), resolve()), 1000));
}

Edit:

In light of the responses I updated to take into consideration return values:

co(function*() {
    console.log('...');
    const result1 = yield one();
    console.log('result1: ', result1);
    const result2 = yield two();
    console.log('result2: ', result2);
    const result3 = yield[one(), two()];
    console.log('result3: ', result3);
    const result4 = yield{
        one: one(),
        two: two()
    };
    console.log('result4: ', result4);
})

function co(gFn) {
    var g = gFn();

    return Promise.resolve().then(go);

    function go() {
        var result = g.next(...arguments);
        if (isPromise(result.value)) {
            return result.value.then(go);
        }
        if (Array.isArray(result.value)) {
            return Promise.all(result.value).then(go);
        }
        if (isObject(result.value)) {
            var o = {};
            var promises = Object.keys(result.value).map(k=>result.value[k].then(r=>o[k] = r));
            return Promise.all(promises).then(()=>o).then(go);
        }
        return Promise.resolve(result);
    }
}

function isPromise(o) {
    return o instanceof Promise;
}

function isObject(val) {
    return val && (Object === val.constructor);
}

function one() {
    return new Promise(resolve=>setTimeout(()=>(console.log('one'),
    resolve('result 1')), 1000));
}

function two() {
    return new Promise(resolve=>setTimeout(()=>(console.log('two'),
    resolve('result 2')), 1000));
}
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • _"Is this basically what `async/await` does under the hood in ESwhatever?"_ Is the inquiry solely to determine the equivalency of one approach to another approach? – guest271314 Apr 21 '17 at 16:27
  • 1
    Have you had a look under the covers of any transpilers to see how they achieve it? – Phil Cooper Apr 21 '17 at 16:28
  • @PhilCooper I have glanced at `co` source. – Ben Aston Apr 21 '17 at 16:32
  • @guest271314 Yes. – Ben Aston Apr 21 '17 at 16:32
  • 1
    Yes, you got the basic idea. But a) you pass the wrong arguments to `goFn` b) your `go` function doesn't `return result.value` c) you've never handled any promise errors (that should lead to `g.throw(…)`) d) `await` does not handle arrays or objects specially …and a few more smaller things. – Bergi Apr 27 '17 at 23:20

2 Answers2

5

Is this basically what async/await does under the hood in ESwhatever?

Not really. It's a different approach for doing sorta the same thing. What async/await turn into is more like

async function foo() {
  const bar = await Bar();
  bar++;
  const baz = await Baz(bar);
  return baz;
}

becomes

function foo() {
  return Bar()
    .then(bar => {
      bar++;
      return Baz(bar)
        .then(baz => {
          return baz;
        });
    });
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • @Bergi But the code you added in your edit (`.then(baz => { return baz; })`) is a no-op. –  Apr 28 '17 at 00:44
  • 1
    And so is `const baz = …; return baz;` :-) [Apart from the additional resolve step it's useless indeed](http://stackoverflow.com/a/41089205/1048572), but the translation of `async`/`await` is mechanical and you've placed a useless `await` in your code, so well… – Bergi Apr 28 '17 at 05:57
  • @torazaburo, hey, can you also please extend your answer to include how the slightly modified example is translated into promises approach? This is the modified example: `const bar = await Bar(); someAsyncFn(); bar++;...`. Is it just executed alongside `bar++;` without any affects on promise chain? – Max Koretskyi May 26 '17 at 08:34
  • @Maximus Yes, that's just a standard function call. – Bergi May 26 '17 at 09:06
  • @Bergi, I got it, thanks. So I can assume that any call to a function without `await` doesn't add `.then` to a prototype chain? – Max Koretskyi May 26 '17 at 09:09
3

Stage 3 Draft / January 26, 2016 Async Functions provides examples of three patterns; Promise; Generator; Async Functions; where the distinct approaches essentially produce the same result

Examples#

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;
}
guest271314
  • 1
  • 15
  • 104
  • 177