源码解析

co@4.6版本不到240行代码,整体来说,还算比较简单。但并不容易阅读

  1. // 核心代码
  2. function co(gen) {
  3. // 缓存this
  4. var ctx = this;
  5. var args = slice.call(arguments, 1)
  6. // we wrap everything in a promise to avoid promise chaining,
  7. // which leads to memory leak errors.
  8. // see https://github.com/tj/co/issues/180
  9. // 重点,co的返回值是Promise对象。为什么可以then和catch的根源
  10. return new Promise(function(resolve, reject) {
  11. // 如果你懂Promise规范,就知道这是解决状态回调,这是首次调用
  12. onFulfilled();
  13. /**
  14. * @param {Mixed} res
  15. * @return {Promise}
  16. * @api private
  17. */
  18. function onFulfilled(res) {
  19. var ret;
  20. try {
  21. ret = gen.next(res);
  22. } catch (e) {
  23. return reject(e);
  24. }
  25. next(ret);
  26. }
  27. /**
  28. * @param {Error} err
  29. * @return {Promise}
  30. * @api private
  31. */
  32. // 如果你懂Promise规范,就知道这是拒绝状态回调
  33. function onRejected(err) {
  34. var ret;
  35. try {
  36. ret = gen.throw(err);
  37. } catch (e) {
  38. return reject(e);
  39. }
  40. next(ret);
  41. }
  42. // generator执行器
  43. // 如果ret.done,返回ret.value
  44. // 否则,
  45. function next(ret) {
  46. // 如果执行完成,直接调用resolve把promise置为成功状态
  47. if (ret.done) return resolve(ret.value);
  48. // 把yield的值转换成promise
  49. // 支持 promise,generator,generatorFunction,array,object
  50. // toPromise的实现可以先不管,只要知道是转换成promise就行了
  51. var value = toPromise.call(ctx, ret.value);
  52. // 成功转换就可以直接给新的promise添加onFulfilled, onRejected。当新的promise状态变成结束态(成功或失败)。就会调用对应的回调。整个next链路就执行下去了。
  53. // 为什么generator可以无限的next下去呢?
  54. // return value.then(onFulfilled, onRejected);意味着,又要执行onFulfilled了
  55. // onFulfilled里调用next(ret);
  56. if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  57. // 如果以上情况都没发生,报错
  58. return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
  59. + 'but the following object was passed: "' + String(ret.value) + '"'));
  60. }
  61. });
  62. }

读此源码要点

  • 必须深刻理解Promise实现,知道构造函数里的onFulfilled和onRejected是什么意思
  • 必须了解generator的执行机制,理解迭代器里的next以及next的返回对象{value:’’,done: true}

核心代码入口是onFulfilled,无论如何第一次的next(ret)是一定要执行的,因为generator必须要next()一下的。

所以next(ret)一定是重点,而且我们看onFulfilled和onRejected里都调用它,也就是所有的逻辑都会丢在这个next(ret)方法里。它实际上是一个状态机的简单实现。

  1. // generator执行器
  2. // 如果ret.done,返回ret.value
  3. // 否则,
  4. function next(ret) {
  5. // 如果执行完成,直接调用resolve把promise置为成功状态
  6. if (ret.done) return resolve(ret.value);
  7. // 把yield的值转换成promise
  8. // 支持 promise,generator,generatorFunction,array,object
  9. // toPromise的实现可以先不管,只要知道是转换成promise就行了
  10. var value = toPromise.call(ctx, ret.value);
  11. // 成功转换就可以直接给新的promise添加onFulfilled, onRejected。当新的promise状态变成结束态(成功或失败)。就会调用对应的回调。整个next链路就执行下去了。
  12. // 为什么generator可以无限的next下去呢?
  13. // return value.then(onFulfilled, onRejected);意味着,又要执行onFulfilled了
  14. // onFulfilled里调用next(ret);
  15. if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  16. // 如果以上情况都没发生,报错
  17. return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
  18. + 'but the following object was passed: "' + String(ret.value) + '"'));
  19. }

情景1: 状态完成

  1. // 如果执行完成,直接调用resolve把promise置为成功状态
  2. if (ret.done) return resolve(ret.value);

情景2: next,跳回onFulfilled,递归

  1. // 成功转换就可以直接给新的promise添加onFulfilled, onRejected。当新的promise状态变成结束态(成功或失败)。就会调用对应的回调。整个next链路就执行下去了。
  2. // 为什么generator可以无限的next下去呢?
  3. // return value.then(onFulfilled, onRejected);意味着,又要执行onFulfilled了
  4. // onFulfilled里调用next(ret);
  5. if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

情景3: 捕获异常

  1. // 如果以上情况都没发生,报错
  2. return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
  3. + 'but the following object was passed: "' + String(ret.value) + '"'));

以上是核心代码说明。之前我们讲了co实际有2种api,有参数和无参数的,很明显以上是无参数的generator执行器,那么有参数的wrap呢?

  1. // 为有参数的generator调用,提供简单包装
  2. co.wrap = function (fn) {
  3. createPromise.__generatorFunction__ = fn;
  4. return createPromise;
  5. function createPromise() {
  6. // 重点,把arguments给fn当参数。
  7. // call和apply是常规js api
  8. return co.call(this, fn.apply(this, arguments));
  9. }
  10. };

通过call和apply组合使用,知识点比较简单,但这样用还是挺巧妙的。

其他的就基本是工具类了,其实也挺有意思的,自己看吧