学习目标
- 了解 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
引入了
generator
和promise
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
函数,直到done
为true
为止。
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
的流程控制。
语法差异
Callback | Promise | Generator | async+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
需要做的事情,是让generator
向async、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
。之前看红宝书的时候,感觉那个晦涩难懂哇,感谢大佬的活动,希望可以坚持下去,随迟但到,随慢但收获很多。 - 还有就是看比较好看,输出笔记比较难,得考虑下次忘记了怎么写才能想起这次理解的意思,得按照自己理解的流程写出来,继续坚持,持续输出!