Implementing coroutine control flow in JavaScript

2019-01-12 12:20发布

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

2条回答
你好瞎i
2楼-- · 2019-01-12 12:59

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;
}
查看更多
仙女界的扛把子
3楼-- · 2019-01-12 13:11

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;
        });
    });
}
查看更多
登录 后发表回答