Generator执行细节分析(co库底层实现原理)
// 单个异步任务执行,如何使用generator函数呢?
// v1: 创建一个generator函数
function * gen ( ) {
let url = 'http://www.baidu.com' ;
let res = yield fetch ( url ) ;
console . log ( res )
}
// 1. 调用Generator函数,获取遍历器对象
let g = gen ( ) ;
// 2. 使用next方法,执行异步任务的第一阶段,fetch(url)
let res = g . next ( ) ;
// 3. res 是一个Promise对象,{ value: Promise { <pending> }, done: false }
res . value . then ( data => {
// 4. 将获取到的数据格式化
return data . json ( ) ;
} ) . then ( data => {
// 5. 然后将格式化之后的数据传进去,调用next函数,会执行输入console.log(res)
g . next ( data ) ;
} )
// v2: 如何执行下面的多个异步函数呢
function * gen ( ) {
// yiled实际上会返回一个Promise对象
var r1 = yield fetch ( 'https://api.github.com/users/github' ) ;
var r2 = yield fetch ( 'https://api.github.com/users/github/followers' ) ;
var r3 = yield fetch ( 'https://api.github.com/users/github/repos' ) ;
console . log ( [ r1 . bio , r2 [ 0 ] . login , r3 [ 0 ] . full_name ] . join ( '\n' ) ) ;
}
// 使用递归的方式来实现
function run ( gen ) {
// 1. 执行自己定义的Generator函数
let g = gen ( ) ;
function next ( data ) {
let res = g . next ( data ) ;
// 执行完毕
if ( res . done ) return ;
res . value . then ( data => {
return data . json ( ) ;
} ) . then ( data => {
next ( data )
} ) ;
}
// 2. 执行next函数
next ( ) ;
}
// 首次执行并调用
run ( gen ) ;
// v3: 上面的代码优化
function run ( gen ) {
// 1. 获取遍历器对象
let g = gen ( ) ;
function next ( data ) {
let res = g . next ( data ) ;
if ( res . done ) return ;
res . value . then ( data => {
// 继续调用下一个Generator函数
next ( data ) ;
} ) ;
}
next ( ) ;
}
// v4: 实现一个run函数,通用版本
function run ( gen ) {
// 1. 获取遍历器对象
let g = gen ( ) ;
// next函数
function next ( data ) {
let res = g . next ( data ) ;
if ( res . done ) return ;
if ( isPromise ( res . value ) ) {
// 如果是一个Promise对象的话
res . value . then ( data => {
next ( data ) ;
} ) ;
}
else {
res . value ( data ) ;
}
}
next ( ) ;
}
// 可以判断一个对象是不是Promise对象
function isPromise ( obj ) {
return typeof obj . then === 'function' ;
}
// v5. 上面代码继续优化
function run ( gen ) {
let g = gen ( ) ;
return new Promise ( ( resolve , reject ) => {
let res ;
function next ( data ) {
try {
res = g . next ( data ) ;
}
catch ( e ) {
return reject ( e ) ;
}
if ( res . done ) return resolve ( res . value ) ;
let val = toPromise ( res . value ) ;
val . then ( data => {
next ( data ) ;
} , err => {
reject ( err ) ;
} ) ;
}
next ( ) ;
} ) ;
}
// 可以转换任意对象到一个Promise
function toPromise ( obj ) {
if ( isPromise ( obj ) ) {
return obj ;
}
else if ( 'function' === typeof obj ) {
return thunkToPromise ( obj ) ;
}
return obj ;
}
// 把一个函数转换为Promise对象
function thunkToPromise ( fn ) {
return new Promise ( ( resolve , reject ) => {
fn ( ( err , res ) => {
if ( err ) return reject ( err ) ;
resolve ( res ) ;
} )
} ) ;
}
// v6. 上面的代码进一步优化
function run ( gen ) {
return new Promise ( function ( resolve , reject ) {
if ( typeof gen == 'function' ) gen = gen ( ) ;
// 如果 gen 不是一个迭代器
if ( ! gen || typeof gen . next !== 'function' ) return resolve ( gen )
onFulfilled ( ) ;
function onFulfilled ( res ) {
var ret ;
try {
ret = gen . next ( res ) ;
} catch ( e ) {
return reject ( e ) ;
}
next ( ret ) ;
}
function onRejected ( err ) {
var ret ;
try {
ret = gen . throw ( err ) ;
} catch ( e ) {
return reject ( e ) ;
}
next ( ret ) ;
}
function next ( ret ) {
if ( ret . done ) return resolve ( ret . value ) ;
var value = toPromise ( ret . value ) ;
if ( value && isPromise ( value ) ) return value . then ( onFulfilled , onRejected ) ;
return onRejected ( new TypeError ( 'You may only yield a function, promise ' +
'but the following object was passed: "' + String ( ret . value ) + '"' ) ) ;
}
} )
}
function isPromise ( obj ) {
return 'function' == typeof obj . then ;
}
function toPromise ( obj ) {
if ( isPromise ( obj ) ) return obj ;
if ( 'function' == typeof obj ) return thunkToPromise ( obj ) ;
return obj ;
}
function thunkToPromise ( fn ) {
return new Promise ( function ( resolve , reject ) {
fn ( function ( err , res ) {
if ( err ) return reject ( err ) ;
resolve ( res ) ;
} ) ;
} ) ;
}
module . exports = run ;
// v7: co是什么呢?
// co 是大神 TJ Holowaychuk 于 2013 年 6 月发布的一个小模块,用于 Generator 函数的自动执行。
// yield 后是一个 Promise
var fetch = require ( 'node-fetch' ) ;
var co = require ( 'co' ) ;
function * gen ( ) {
var r1 = yield fetch ( 'https://api.github.com/users/github' ) ;
var json1 = yield r1 . json ( ) ;
var r2 = yield fetch ( 'https://api.github.com/users/github/followers' ) ;
var json2 = yield r2 . json ( ) ;
var r3 = yield fetch ( 'https://api.github.com/users/github/repos' ) ;
var json3 = yield r3 . json ( ) ;
console . log ( [ json1 . bio , json2 [ 0 ] . login , json3 [ 0 ] . full_name ] . join ( '\n' ) ) ;
}
// 可以让Generator里面的函数按顺序执行
co ( gen ) ;