实现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“的注意事项:

  1. 它接管一个generator生成器函数并返回一个新函数;
  2. 当返回的函数被调用时,它应该返回一个Promise期约。Promise期约该当对generator生成器函数的返回值有所解决;
  3. 返回函数的类型特色应该和传入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函数的递归调用.