关于typescript:算法思维体操基于generator生成器自己实现AsyncAwaitTypeScript

38次阅读

共计 11106 个字符,预计需要花费 28 分钟才能阅读完成。

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

正文完
 0