实现 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 函数的递归调用.