This vanilla ES6 function async allows code to yield (i.e. await) the asynchronous result of any Promise within. The usage is almost identical to ES7's async/await keywords.
async/await control flow is promising because it allows the programmer to reason linearly about complex asynchronous code. It also has the benefit of unifying traditionally disparate synchronous and asynchronous error handling code into one try/catch block.
This is expository code for the purpose of learning ES6. It is not 100% robust. If you want to use this style of code in the real world you might want to explore a well-tested library like co, task.js or use async/await with Babel. Also take a look at the official async/await draft section on desugaring.
- node.js - 4.3.2+ (maybe earlier with --harmony flag)
- browser - (if you have Promises && Generators)
- babel 👍
{ "presets": ["es2015"] }
- async/await draft - desugaring
- Promises
- Iterators & Generators
- Basic of ES6 Generators by Kyle Sympson
- Return
Promisefromasyncto support chaining - Support other asynchronous actions like thunks or nested generators
/**
* @param {Generator} gen Generator which yields Promises
* @param {Any} context last resolved Promise value, currently only used recursively
*/
function async(gen, context = undefined) {
const generator = typeof gen === 'function' ? gen() : gen; // Create generator if necessary
const { value: promise } = generator.next(context); // Pass last result, get next Promise
if ( typeof promise !== 'undefined' ) {
promise.then(resolved => async(generator, resolved))
.catch(error => generator.throw(error)); // Defer to generator error handling
}
}
/* Usage */
async(function* () { // Generators can't be declared with arrow syntax
try {
// Execution is paused until the yielded promise resolves
console.log(yield Promise.resolve('A Mortynight Run'))
// Promises already provide support for concurrent async actions.
// Execution will not continue until both a & b are fulfilled
const [a,b] = yield Promise.all([
Promise.resolve('Get Schwifty'),
Promise.resolve('The Ricks Must Be Crazy')
]);
console.log(a + ' ' + b);
// Rejected promises will be handled by try/catch as if code was synchronous
const seasonTwoFinale = yield Promise.reject(new Error('Tammy'));
// Never executed
const seasonThree = 'Coming Soon';
} catch (error) { console.error(error.message); } // Tammy
})
May I suggest renaming some variables for clarity:
Some other things to point out that seem missing here:
an
async(..)call should return a promise, as all calls toasync functions do. This promise should resolve to the final return value of the generator.If I
yield 42(aka,await 42), that's supposed to get wrapped in an immediately resolved promise and thus fed right back into the generator. Here, you assume it's always a promise, and call.then(..)on it. You should probably usePromise.resolve(..)on the return value, for safety sake.If a
yielded promise rejects, you correctlythrow(..)it back into the generator... but if the generator has atry..catcharound thatyieldand catches thethrow()n error, and then subsequently yields/returns another promise or value, your runner is not capturing that value inresolvedand continuing the loop by passing it back to the nextnext(..)call. In other words, your runner will stop prematurely. It breaks on code like this:Your detection of the generator being finished is flawed, assuming
value: undefinedmeans it's complete. That's not a reliable assumption. I couldreturn 42from my generator, in which case that value would come out withvalue: 42, done: trueand wouldn't detect that it was complete, so you'd try to resume an already complete generator. Or, I couldyield undefined, which would bevalue: undefined, done: false, meaning the generator is most definitely not complete, but your runner would end prematurely.Your
async(..)helper isn't supporting passing any arguments into the original generator, the way you can when actually calling anasync function.To address these issues (and others), I have a commented full-implementation of a promise-aware generator runner (emulating the sync-async style as your's is) here: https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch4.md#promise-aware-generator-runner