【源码】co原理源码解读

学习目标

  • 了解 co 是什么
  • promise 、generator、async await 三者区别和联系
  • 熟悉巩固 Promise、generator、async await 的用法
  • 熟悉 co 的实现原理
准备工作
  • 代码地址:https://github.com/lxchuan12/koa-analysis,传送门
  • co 源码地址:https://github.com/tj/co
  • 克隆源码后,入口文件是koa/examples/koa-convert/co.js

了解区别联系

javascript的运行机制是单线程处理,即只有上一个任务完成后,才会执行下一个任务,这种机制也被称为“同步”。
“同步”的最大缺点,就是如果某一任务运行时间较长,其后的任务就无法执行。这样会阻塞页面的渲染,导致页面加载错误或是浏览器不响应进入假死状态。
如果这段任务采用“异步”机制,那么它就会等到其他任务运行完后再执行,不会阻塞线程

es6之前实现异步方式
  • 回调函数
  • 事件监听
  • 发布/订阅
es6

引入了 generatorpromise

es7

增加了async/await。很好的解决了回调函数的异步执行和嵌套问题。

一张图说明

在这里插入图片描述

promise 、generator、async await 三者区别和联系
  • generator +(co) + promise = async/await
  • 发展的历程算是 callback -> promise -> generator -> async + await
generator
  • 语法:function* 函数名(参数){函数体}
  • 使用:
    • 当调用一个generator函数时,会返回一个iterator对象(迭代器对象)
    • 通过next()得到{value: xxx, done: xxx} 这类的数据
    • value值:为yield表达式返回的值,也是本次返回的值,如yield 'aa'则{value: aa, done: xxx}
    • done值:表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回,值为false表示没处理完,反之。
    • 例子:
    function* gen() {
        yield 'yuyuyu';
        yield 'uuiuiuiu';
        return true;
    }
    
    // 获取迭代器对象
    var iterator = gen();
    
    // 调用next()获取{value: xxx, done: xxx}对象
    var p1 = iterator.next();
    var p2 = iterator.next();
    var p3 = iterator.next();
    console.log(p1);// {value: 'yuyuyu', done: false}
    console.log(p2);// {value: 'uuiuiuiu', done: false}
    console.log(p3);// {value: true, done: true}
    
  • 写法1
    var p1 = iterator.next(); console.log(p1);// {value: 'yuyuyu', done: false} p1.then(res =>console.log(res, '0-0-0-0-0-)) //balalaalalal,0-0-0-

  • 写法2
    var {value, done} = iterator.next(); value.then(res => console.log(res)) balalaalalal

co 是什么?

co是用于generator函数(生成器函数)的自动执行。
co是根据生成器的特性和Promise结合创造出来的一个自动生成器。co(generatoe + Promise)
使得 generator 和 yield 的语法更趋向于同步编程的写法
异步编程的语法目标:就是让它更像同步编程

co产生的原因
  • 上述例子可以看出,每次都需要手动的调用next()方法来移动迭代器的指针
  • co就是不需要手动的调用next()函数,他会根据你传入生成器函数来自动执行next函数,直到donetrue为止。
co源码解读
  • 截取参数
/**
 * slice() reference.
 * slice 是Array.prototype.slice的简写,用于将函数参数转化为数组。
 * 方便截取参数
 */

var slice = Array.prototype.slice;
  • 导出co
/**
 * Expose `co`.
 * 导出co
 */

module.exports = co['default'] = co.co = co;

引入方式汇总:

const co = require('co')
require('co').co
import co from 'co'
import * as co from 'co'
import { co } form 'co'
  • co.wrap
/**
 * Wrap the given generator `fn` into a
 * function that returns a promise.
 * This is a separate(单独的) function so that
 * every `co()` call doesn't create a new,
 * unnecessary closure.
 * 返回一个创建promise的函数,是为了方便重复调用同一个异步流程
 * 将generator 函数转换成promise函数 可以重复使用,类似于缓存函数功能
 * @param {GeneratorFunction} fn
 * @return {Function}
 * @api public
 */

co.wrap = function (fn) {
  // 借助高阶函数特性,返回一个新函数createPromise,然后传给他的参数都会被导入到Generator函数中
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};

例子

例子🌰
查中文名1
co(function* () {
const chineseName = yield searchChineseName('tom')
return chineseName
})
查中文名2
co(function* () {
const chineseName = yield searchChineseName('jarry')
return chineseName
})
无法复用,通过co.wrap实现重复利用
const getChineseName = co.wrap(function* (name) {
const filename = yield searchChineseName(name)
return filename
})
getChineseName('tom').then(res => {})
getChineseName('jarry').then(res => {})
  • 主要源码部分
/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

// co函数的参数为Generator 函数,返回结果为promise
function co(gen) {
  // 获取上下文this
  var ctx = this;
  // 获取传入函数后所有参数 call和apply : 用来改变对象中函数内部的this引用
  var args = slice.call(arguments, 1);
  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  // 返回一个promise
  return new Promise(function(resolve, reject) {
    // 把ctx和参数传递给gen函数并执行,判断是不是函数
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    // 如果不是函数 直接返回   判断 gen.next 是否函数,如果不是直接 resolve(gen)
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

  /**
    * 到这里的时候 gen结构为
    * '{ value: xxx, next: function() {},done: true or false}'
    * 这里 先去走第一个yield,因为还没走到gen.next() 所以不需要传参数
    * 先执行一次 next
    */

    onFulfilled();
    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */
    // 执行 gen.next 函数,获取 gen 的值
    function onFulfilled(res) {
      var ret;
      try {
        // 调用gen.next传参数res,得到yiel的结果
        ret = gen.next(res);
      } catch (e) {
        // 出错直接返回
        return reject(e);
      }
      next(ret); //接收gen.next返回结果,去走判断逻辑
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */
    //gen 执行 throw 方法,出错直接reject
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
    // 这里return 只是为了结束函数执行 return结果没有实际意义,切勿混淆

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */
    // 反复执行调用自己
    function next(ret) {
      // 检查当前是否为 Generator 函数的最后一步,如果是就返回resolve
      if (ret.done) return resolve(ret.value);
      // done 为 false,说明没走完, 把 ret.value 转成 promise
      // 确保返回值是promise对象。
      var value = toPromise.call(ctx, ret.value);
      // 如果转化完成 结果为peomise, 那么继续链式 onFulfilled, onRejected 操作
      // 使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      // 简而言之 转换的不是promise的话 就抛出错误
      // 在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}
辅助函数
  • toPromise
// 辅助函数
/**
 * Convert a `yield`ed value into a promise.
 *
 * @param {Mixed} obj
 * @return {Promise}
 * @api private
 */

function toPromise(obj) {
  // 假值 直接返回
  if (!obj) return obj;
  // 是promise 直接返回
  if (isPromise(obj)) return obj;
  // 是 generator生成器函数 或者 generator迭代器对象 调用 co 返回
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // 为函数 转换为promise返回
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  // 为数组 转换为promise返回
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  // 为对象 转换为promise返回
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}
  • thunkToPromise
/**
 * Convert a thunk to a promise.
 * [THUNK](https://www.ruanyifeng.com/blog/2015/05/thunk.html)
 *
 * @param {Function}
 * @return {Promise}
 * @api private
 */
/** 用thunk方式去将函数转化为promise
 * thunk 是一个零时函数,在执行时求职 就是“传名调用”
 * 先将一个参数放到一个临时函数之中,在将这个临时函数传入函数题,这个临时函数就叫做'Thunk'函数
 * 主要是用来替换某个表达式的值
 * 在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
 * function f(m){
      return m * 2;
    }

    f(x + 5);

    // 等同于

    var thunk = function () {
      return x + 5;
    };

    function f(thunk){
      return thunk() * 2;
    }
 * 上面代码中,函数 f 的参数 x + 5 被一个函数替换了。凡是用到原参数的地方,对 Thunk 函数求值即可。
 *  */

function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}
  • arrayToPromise
/**
 * Convert an array of "yieldables" to a promise.
 * Uses `Promise.all()` internally.
 * 将数组的每一项转化为promise调用promise.all返回
 *
 * @param {Array} obj
 * @return {Promise}
 * @api private
 */

function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
  • objectToPromise
/**
* Convert an object of "yieldables" to a promise.
* Uses `Promise.all()` internally.
* 将数组的每一项转化为promise调用promise.all返回
* @param {Object} obj
* @return {Promise}
* @api private
*/

function objectToPromise(obj){
 // 初始化result
 var results = new obj.constructor();
 // 遍历所有的键名
 var keys = Object.keys(obj);
 // promise 组成的数组
 var promises = [];
 for (var i = 0; i < keys.length; i++) {
   var key = keys[i];
   // 将每一项转化为promise
   var promise = toPromise.call(this, obj[key]);
   // 如果为promise 则调用 defer
   //  defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
   if (promise && isPromise(promise)) defer(promise, key);
   else results[key] = obj[key];
 }
 return Promise.all(promises).then(function () {
   return results;
 });
  • defer
  // 将promise resolve 结果塞到数组里
 function defer(promise, key) {
   // predefine the key in the result
   // 先赋值为undefined
   results[key] = undefined;
   // 塞到promises数组里,并且把promise resolve的结果存到result里
   promises.push(promise.then(function (res) {
     results[key] = res;
   }));
 }
}
  • isPromise、isGenerator、isGeneratorFunction、isObject
/**
 * Check if `obj` is a promise.
 * 判断obj 是不是promise
 * @param {Object} obj
 * @return {Boolean}
 * @api private
 */

function isPromise(obj) {
  return 'function' == typeof obj.then;
}

/**
 * Check if `obj` is a generator.
 * 判断obj 是不是generator迭代器对象
 * @param {Mixed} obj
 * @return {Boolean}
 * @api private
 */

function isGenerator(obj) {
  return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}

/**
 * Check if `obj` is a generator function.
 * 判断obj 是不是generator生成器函数
 * @param {Mixed} obj
 * @return {Boolean}
 * @api private
 */
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
  return isGenerator(constructor.prototype);
}

/**
 * Check for plain object.
 *  判断是否为对象
 * @param {Mixed} val
 * @return {Boolean}
 * @api private
 */

function isObject(val) {
  return Object == val.constructor;
}

了解完co 再来看看 async await

async await
  • async 对应的是 *

  • await 对应的是 yield

async/await 自动进行了 Generator 的流程控制。

语法差异
CallbackPromiseGeneratorasync+await + Promise
ajax(url, () => {})Promise((resolve,reject) => { resolve() }).then()function* gen() { yield 1}async getData() { await fetchData() }
理解归纳

看完上述知识点,我们在对比一下promise + then 的例子:

先写个方法 模拟一下请求
function getData() {
	return new Promise ( function(resolve, reject) {
		resolve(“balalaalalal”);
	})
}
getData().then(function(res) {
	// 处理异步的结果
	console.log(res, '第一个res'); //balabaka,第一个res
	  // 返回第二个异步
    return Promise.resolve(2222)
}).
then(function(res) {
    // 处理第二个异步的结果
    console.log(res, ‘---)//2222,---
})
.catch(function(err) {
    console.error(err);
})

看上面的例子:有多少个异步处理就会有多少个then,来处理异步之间可能存在的同步关系,因为then 的处理是一层一层的嵌套的
yield来 接收值的例子

function* gen() {
    yield getData()
}
// 获取迭代器对象
var iterator = gen();

// 调用next()获取{value: xxx, done: xxx}对象
var p1 = iterator.next();
p1.value.then(res => console.log(res, '0-0-0-')) // balalaalalal,0-0-0-

generator不自动执行,还需要调用next(),调用一次才走一次

所以才有了async、await例子

async function asyncFunc() {
	const res = await getData();
	console.log(res, 'asyncFunc-res await 函数 自动执行');
}
asyncFunc(); // 输出结果

(async function getData() {
  var res = await getData();
  console.log(res)
})()

也就是说co需要做的事情,是让generatorasync、await函数一样自动执行。

简易co

核心实现:
1)函数传参
2)generator.next 自动执行

function co(gen) {
	//1.传参
	var ctx = this;
	var args = slice.call(arguments,1);
	return new Primise(function (resolve,reject) {
		//2.自动执行 next()
		onFulfilled();

		function onFulfilled(res) {
			var res = gen.next();
			next(res);
		}
	})

	function next(res) {
		if(res.done) return resolve(res.value);
		var promise = res.value;
		promise && promise.thrn(onFulfilled);
	}
}

// 执行
co(function* fetchData() {
  var result = yield getData();
  // 1s后打印 {data: "balalaalalal”"}
  console.log(result)
})


感受总结
  • 写到这里终于写完了,断断续续间隔了好久,来长出一口气,呼~
  • 之前面试老是会问 promise 接着就问async await. 当时很懵,只在死记硬背搞面试,现在不仅知道了历史进程,还理清了原理,尤其是generator。之前看红宝书的时候,感觉那个晦涩难懂哇,感谢大佬的活动,希望可以坚持下去,随迟但到,随慢但收获很多。
  • 还有就是看比较好看,输出笔记比较难,得考虑下次忘记了怎么写才能想起这次理解的意思,得按照自己理解的流程写出来,继续坚持,持续输出!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值