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));
}

5
  • "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? Commented Apr 21, 2017 at 16:27
  • 1
    Have you had a look under the covers of any transpilers to see how they achieve it? Commented Apr 21, 2017 at 16:28
  • @PhilCooper I have glanced at co source. Commented Apr 21, 2017 at 16:32
  • @guest271314 Yes. Commented Apr 21, 2017 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. Commented Apr 27, 2017 at 23:20

2 Answers 2

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;
        });
    });
}
Sign up to request clarification or add additional context in comments.

5 Comments

@Bergi But the code you added in your edit (.then(baz => { return baz; })) is a no-op.
And so is const baz = …; return baz; :-) Apart from the additional resolve step it's useless indeed, but the translation of async/await is mechanical and you've placed a useless await in your code, so well…
@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?
@Maximus Yes, that's just a standard function call.
@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?
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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.