实现Async/Await
要挑战的工作是应用JavaScript的generator生成器来实现Async/Await。
问题形容
上面是一个Async/Await函数的示例。
async function doSomething(value) { const result1 = await fetchFromNetwork(value + '-1'); const result2 = await fetchFromNetwork(value + '-2'); try { const result3 = await failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2;}doSomething('http://google.com') .then(r => console.log(`Got result: ${r}`)) .catch(console.error)
咱们须要应用generator生成器和一个特地的封装函数“asynk”来实现同样性能。等效的示例为:
const doSomething = asynk(function* (value) { const result1 = yield fetchFromNetwork(value + '-1'); const result2 = yield fetchFromNetwork(value + '-2'); try { const result3 = yield failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2;});doSomething('http://google.com') .then(r => console.log(`Got result: ${r}`)) .catch(console.error)
对于“asynk“的注意事项:
- 它接管一个generator生成器函数并返回一个新函数;
- 当返回的函数被调用时,它应该返回一个Promise期约。Promise期约该当对generator生成器函数的返回值有所解决;
- 返回函数的类型特色应该和传入generator生成器函数的类型特色匹配。惟一的例外是,如果generator生成器函数返回一个非Promise期约的类型,返回函数应该返回一个与那个类型绝对应的Promise期约。
待处理事项
- 如果违心的话,你能够先实现无类型的计划。有些人感觉应用类型有所帮忙,另外一些人则感觉后续增加类型更容易;
- 先关注控制流,而后才是参数值返回值可能有所帮忙。
规定
- 你不能应用原生的Async/Await;
- 请不要间接查阅与”如何应用generator生成器实现Async/Await“相干的网上材料。
参考文献
上面是一些你会感觉有用的链接。请悉听尊便拜访这些链接,但务必仅限于此。
- https://developer.mozilla.org...
- https://developer.mozilla.org...
上面的类型定义或者有所裨益:
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>;}interface IteratorYieldResult<TYield> { done?: false; value: TYield;}interface IteratorReturnResult<TReturn> { done: true; value: TReturn;}type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
TypeScript代码模板:
// See README.md for instructions.// TODO: add type annotations.function asynk(fn: any) { // YOUR CODE HERE}// Playgroud for testing the code.console.clear();function* countUp() { for (let i = 0; i < 10; i++) { yield i; }}const g = countUp();console.log(g.next());console.log(g.next());// const playground = asynk(function* () {// const result = yield Promise.resolve('hello');// });// playground().catch(console.error)
最终实现:
无类型的JavaScript版本:
function asynk(fn) { // YOUR CODE HERE return (...args) => new Promise((resolve, reject) => { // Initialize the generator function, which might have signatures, // Extract the next() queue fisrt, then iterate another initialized generator const runner = fn(...args); try { let promiseCallbackQueue = [ (res, slaveRunner) => slaveRunner.next(res).value, ]; let result = runner.next(); // First round, collect promiseCallback while (!result.done && String(result.value) === '[object Promise]') { promiseCallbackQueue.push( (res, slaveRunner) => slaveRunner.next(res).value ); result = runner.next(); } /** Second round, iterate another generator promise chain and pass promised value, by using the trick of event loop setTimeout -> MacroQueue Promise resolve -> MicroQueue For each setTimeout, its inner promise resolve will call in advance of the latter setTimeout, because of MicroQueue Drawback: initialize the given generator for twice. */ let medium; let pcqLen = promiseCallbackQueue.length; const slaveRunner = fn(...args); for (let i = 0; i < pcqLen; i++) { setTimeout(() => { const pm = promiseCallbackQueue[i](medium, slaveRunner); medium = pm; // To get the final yield value, would be Promise fisrt if (String(pm) === '[object Promise]') { pm.then((res) => { medium = res; // get the input value to yield function }); } else { resolve(medium); } }, 0); } } catch (e) { // catch operation return reject(e); } });}console.clear();function* countUp() { for (let i = 0; i < 10; i++) { yield i; }}const g = countUp();console.log(g.next());console.log(g.next());const playground = asynk(function* () { const result = yield Promise.resolve('hello'); return result;});playground() .then((r) => console.log(r, 'yes')) .catch(console.error);const fetchFromNetwork = (val) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val) => { return Promise.resolve(val); // return Promise.reject(val);};const doSomething = asynk(function* (value) { const result1 = yield fetchFromNetwork(value + '-1'); const result2 = yield fetchFromNetwork(value + '-2'); try { const result3 = yield failedFetchFromNetwork().catch((err) => { console.error(err); }); } catch (error) { console.error('Error fetching from network'); } return result1 + " " + result2;});doSomething('http://google.com') .then((r) => console.log(`Got result: ${r}`)) .catch(console.error);// 打印后果:// { value: 0, done: false }// { value: 1, done: false }// 'hello' 'yes'// 'Got result: http://google.com-1 http://google.com-2'
思路:先应用一个while循环遍历generator生成器收集next次数,而后for循环再遍历generator生成器,前后传递生成器Promise.then失去的值,窍门是应用setTimeout属于宏队列,promise属于微队列,同一次事件循环中setTimeout总会先于promise执行这一JS异步编程个性。
不足之处:generator生成器函数会被执行两次,如果在其中有申明console的话,会让人感觉有些奇怪,然而最终的返回值后果倒是正确。
含类型的TypeScript版本:
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>;}interface IteratorYieldResult<TYield> { done?: false; value: TYield;}interface IteratorReturnResult<TReturn> { done: true; value: TReturn;}type IteratorResult<T, TReturn = any> = | IteratorYieldResult<T> | IteratorReturnResult<TReturn>;function asynk(fn: (...args: any) => Generator<Promise<any>, any, string>) { // YOUR CODE HERE return (...args: any) => new Promise((resolve, reject) => { // Initialize the generator function, which might have signatures, // Extract the next() queue fisrt, then iterate another initialized generator const runner = fn(...args); try { let promiseCallbackQueue = [ (res: any, slaveRunner: any) => slaveRunner.next(res).value, ]; let result = runner.next(); // First round, collect promiseCallback while (!result.done && String(result.value) === '[object Promise]') { promiseCallbackQueue.push( (res, slaveRunner) => slaveRunner.next(res).value, ); result = runner.next(); } /** Second round, iterate another generator promise chain and pass promised value, by using the trick of event loop setTimeout -> MacroQueue Promise resolve -> MicroQueue For each setTimeout, its inner promise resolve will call in advance of the latter setTimeout, because of MicroQueue Drawback: initialize the given generator for twice. */ let medium: Promise<any> | string; let pcqLen = promiseCallbackQueue.length; const slaveRunner = fn(...args); for (let i = 0; i < pcqLen; i++) { setTimeout(() => { const pm = promiseCallbackQueue[i](medium, slaveRunner); medium = pm; // To get the final yield value, would be Promise fisrt if (String(pm) === '[object Promise]') { pm.then((res: string) => { medium = res; // get the input value to yield function }); } else { resolve(medium); } }, 0); } } catch (e) { // catch operation return reject(e); } });}console.clear();function* countUp() { for (let i = 0; i < 10; i++) { yield i; }}const g = countUp();console.log(g.next());console.log(g.next());const playground = asynk(function* () { const result = yield Promise.resolve('hello'); return result;});playground() .then(r => console.log(r, 'yes')) .catch(console.error);const fetchFromNetwork = (val: string) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val: string) => { return Promise.resolve(val); // return Promise.reject(val);};const doSomething = asynk(function* (value) { const result1 = yield fetchFromNetwork(value + '-1'); const result2 = yield fetchFromNetwork(value + '-2'); try { const result3 = yield failedFetchFromNetwork(value).catch(err => { console.error(err); }); console.log(result3); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2;});doSomething('http://google.com') .then(r => console.log(`Got result: ${r}`)) .catch(console.error);// 打印后果:// { value: 0, done: false }// { value: 1, done: false }// { result3: undefined }// 'hello' 'yes'// { result3: 'http://google.com' }// 'Got result: http://google.com-1 http://google.com-2'
解决准则: 让TypeScript校验不显红即可, 适当应用any和string.
其余实现参考:
Babel 7,面向chrome 54
Babel Repl
输出:
const fetchFromNetwork = (val) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val) => { return Promise.resolve(val); // return Promise.reject(val);};async function doSomething(value) { const result1 = await fetchFromNetwork(value + '-1'); const result2 = await fetchFromNetwork(value + '-2'); try { const result3 = await failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2;}doSomething('http://google.com') .then(r => console.log(`Got result: ${r}`)) .catch(console.error)
输入:
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); }}function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); } _next(undefined); }); };}const fetchFromNetwork = (val) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val) => { return Promise.resolve(val); // return Promise.reject(val);};function doSomething(_x) { return _doSomething.apply(this, arguments);}function _doSomething() { _doSomething = _asyncToGenerator(function* (value) { const result1 = yield fetchFromNetwork(value + '-1'); const result2 = yield fetchFromNetwork(value + '-2'); try { const result3 = yield failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2; }); return _doSomething.apply(this, arguments);}doSomething('http://google.com') .then((r) => console.log(`Got result: ${r}`)) .catch(console.error);
精髓之处:_next和_throw函数的递归调用。
TypeScript 4.9.5,面向ES2015
TypeScript Playground
输出:
const fetchFromNetwork = (val) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val) => { return Promise.resolve(val); // return Promise.reject(val);};async function doSomething(value) { const result1 = await fetchFromNetwork(value); const result2 = await fetchFromNetwork(value); try { const result3 = await failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + result2;}doSomething('http://google.com') .then(r => console.log(`Got result: ${r}`)) .catch(console.error)
输入:
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); };const fetchFromNetwork = (val) => { return Promise.resolve(val);};const failedFetchFromNetwork = (val) => { return Promise.resolve(val); // return Promise.reject(val);};function doSomething(value) { return __awaiter(this, void 0, void 0, function* () { const result1 = yield fetchFromNetwork(value + '-1'); const result2 = yield fetchFromNetwork(value + '-2'); try { const result3 = yield failedFetchFromNetwork(); } catch (error) { console.error('Error fetching from network'); } return result1 + ' ' + result2; });}doSomething('http://google.com') .then((r) => console.log(`Got result: ${r}`)) .catch(console.error);
精髓之处: step函数的递归调用.