# 写在前面
最近发现了一个叫 co 的神奇东西,似乎是 es7 没发布时,没有 async,await 时的一个非常好用的库,感觉挺好玩的,打算模拟实现一个。
# 实现过程
# 只能执行同步代码版
这一版的代码十分简单,主要做到三点就行了
- 把返回的 promise 的 resolve 函数一层层往下传,当迭代结束时,把生成器函数的返回值传给 resolve 函数
- 如果没有结束迭代,就把上一次迭代获得的值作为下一次 next 函数执行的参数传入,一直重复执行直到迭代结束为止
function run(generatorFunc, data) { | |
// 获得生成器 | |
const generator = generatorFunc(data); | |
// 执行 next 方法获取结果 | |
let result = execute(generator); | |
return new Promise((resolve, reject) => { | |
// 处理结果 | |
handleResult(generator, result, resolve, reject); | |
}); | |
} | |
function handleResult(generator, result, resolve, reject) { | |
// 如果 result.done 是真,证明已经迭代完了,而且第一次为 true 时的 value 是生成器函数的返回值 | |
// 所以我们通过 resolve 可以把返回值传给 run 函数外的 then | |
if (result.done){ | |
resolve(result.value); | |
return; | |
} | |
// 如果没有结束,就继续执行,同时把上一个 next 返回的 value 传回去 | |
result = execute(generator, result.value); | |
// 这里的 generator, resolve, reject 都是一层一层往下传不会变的 | |
// 因为要执行的是同一个生成器,要接收返回值和错误原因的也是同一个函数,都是要传给 run 函数返回的 promise 绑定的 then | |
handleResult(generator, result, resolve, reject); | |
} | |
// 就是执行 next 方法 | |
function execute(generator, data) { | |
return generator.next(data); | |
} |
function run(generatorFunc, data) { | |
// 获得生成器 | |
const generator = generatorFunc(data); | |
// 执行 next 方法获取结果 | |
let result = execute(generator); | |
return new Promise((resolve, reject) => { | |
// 处理结果 | |
handleResult(generator, result, resolve, reject); | |
}); | |
} | |
function handleResult(generator, result, resolve, reject) { | |
// 如果 result.done 是真,证明已经迭代完了,而且第一次为 true 时的 value 是生成器函数的返回值 | |
// 所以我们通过 resolve 可以把返回值传给 run 函数外的 then | |
if (result.done){ | |
resolve(result.value); | |
return; | |
} | |
// 如果没有结束,就继续执行,同时把上一个 next 返回的 value 传回去 | |
result = execute(generator, result.value); | |
// 这里的 generator, resolve, reject 都是一层一层往下传不会变的 | |
// 因为要执行的是同一个生成器,要接收返回值和错误原因的也是同一个函数,都是要传给 run 函数返回的 promise 绑定的 then | |
handleResult(generator, result, resolve, reject); | |
} | |
// 就是执行 next 方法 | |
function execute(generator, data) { | |
return generator.next(data); | |
} |
测试代码
run(function * () { | |
let name = yield "sena"; | |
let age = yield 16; | |
return { | |
name, | |
age | |
} | |
}).then((data) =>; { | |
console.log(data); // { name: 'sena', age: 16 } | |
}, (err) =>; { | |
console.log("error:",err); | |
}); |
# 能执行异步代码版
这次的代码也不难,主要是增加了处理执行 next 方法时返回值是一个 promise 的逻辑,大致思路是,把执行下次 next 的代码放到返回 promise 的 then 中,也就是当返回的 promise 执行了 resolve 方法,才会触发 then 中绑定的 generator.next () 方法
function run(generatorFunc, data) { | |
// 获得生成器 | |
const generator = generatorFunc(data); | |
// 执行 next 方法获取结果 | |
let result = execute(generator); | |
return new Promise((resolve, reject) => { | |
// 处理结果 | |
handleResult(generator, result, resolve, reject); | |
}); | |
} | |
function handleResult(generator, result, resolve, reject) { | |
// 如果 result.done 是真,证明已经迭代完了,而且第一次为 true 时的 value 是生成器函数的返回值 | |
// 通过 resolve 可以把返回值传给 run 函数外的 then | |
if (result.done){ | |
resolve(result.value); | |
return; | |
} | |
// 如果值是个 promise | |
if (isPromise(result.value)){ | |
// 把 next () 放到 then 里面执行 | |
result.value.then((data) => { | |
result = execute(generator, data); | |
handleResult(generator, result, resolve, reject); | |
}) | |
} else { | |
// 如果没有结束,就继续执行,同时把上一个 next 返回的 value 传回去 | |
result = execute(generator, result.value); | |
// 这里的 generator, resolve, reject 都是一层一层往下传不会变的 | |
// 因为要执行的是同一个生成器,要接收返回值和错误原因的也是同一个函数,都是要传给 run 函数返回的 promise 绑定的 then | |
handleResult(generator, result, resolve, reject); | |
} | |
} | |
// 就是执行 next 方法 | |
function execute(generator, data) { | |
return generator.next(data); | |
} | |
// 用于判断返回值是不是 promise | |
function isPromise(obj) { | |
return 'function' == typeof obj.then; | |
} |
测试代码
function * requestData() { | |
let name = yield api1(); | |
console.log(name); // 1s 后输入 "sena" | |
let age = yield api2(); | |
console.log(age); // 2s 后输出 16 | |
return { | |
name, | |
age | |
}; | |
} | |
function api1() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve("sena"); | |
}, 1000); | |
}) | |
} | |
function api2() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve(16); | |
}, 1000); | |
}) | |
} | |
run(requestData).then((res) => { | |
console.log(res); // 2s 后输出 {name: 'sena', age: 16} | |
}); |
加入错误处理
到这里代码就开始有意思起来了,主要是实现下面两点
- 用 try catch 捕获异步的异常
- 如果没有 try catch 就执行 reject 方法
这里我们会用到 generator.throw 方法,用于把 reject 的情况转化成 error 传给生成器内的 try catch
function run(generatorFunc, data) { | |
// 获得生成器 | |
const generator = generatorFunc(data); | |
// 执行 next 方法获取结果 | |
let result = execute(generator); | |
return new Promise((resolve, reject) => { | |
// 处理结果 | |
handleResult(generator, result, resolve, reject); | |
}); | |
} | |
function handleResult(generator, result, resolve, reject) { | |
// 如果 result.done 是真,证明已经迭代完了,而且第一次为 true 时的 value 是生成器函数的返回值 | |
// 通过 resolve 可以把返回值传给 run 函数外的 then | |
if (result.done){ | |
resolve(result.value); | |
return; | |
} | |
// 如果值是个 promise | |
if (isPromise(result.value)){ | |
// 把 next () 放到 then 里面执行 | |
result.value.then((data) => { | |
result = execute(generator, data); | |
handleResult(generator, result, resolve, reject); | |
}, (err) => | |
// 当返回的 promise 执行 reject 时 | |
{ | |
// 在外面包一层 try catch, 如果生成器函数里没有写 try catch, 就会在这里捕获 error | |
try { | |
// 把错误抛入生成器 | |
result = throwError(generator, err); | |
// 如果错了 try catch 就继续处理 | |
handleResult(generator, result, resolve, reject); | |
}catch (e) { | |
// 如果在这里接收到错误,证明生成器里的错误没有被捕获,执行绑定的 reject | |
reject(e); | |
} | |
}) | |
} else { | |
// 如果没有结束,就继续执行,同时把上一个 next 返回的 value 传回去 | |
result = execute(generator, result.value); | |
// 这里的 generator, resolve, reject 都是一层一层往下传不会变的 | |
// 因为要执行的是同一个生成器,要接收返回值和错误原因的也是同一个函数,都是要传给 run 函数返回的 promise 绑定的 then | |
handleResult(generator, result, resolve, reject); | |
} | |
} | |
// 就是执行 next 方法 | |
function execute(generator, data) { | |
return generator.next(data); | |
} | |
// 用于把错误抛入生成器 | |
function throwError(generator, error) { | |
return generator.throw(error); | |
} | |
// 用于判断返回值是不是 promise | |
function isPromise(obj) { | |
return 'function' == typeof obj.then; | |
} |
测试代码
function * requestData() { | |
let name = yield api1(); | |
console.log(name); // 1s 后输入 "sena" | |
let age = yield api2(); | |
console.log(age); | |
return { | |
name, | |
age | |
}; | |
} | |
function api1() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve("sena"); | |
}, 1000); | |
}) | |
} | |
function api2() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
reject("api2炸了"); | |
}, 1000); | |
}) | |
} | |
run(requestData).then((res) => { | |
console.log(res); | |
}, (err) => { | |
console.log("错误原因 : " + err); // 错误原因 : api2 炸了 | |
}); |
还是测试代码
function * requestData() { | |
let name = yield api1(); | |
console.log(name); // 1s 后输入 "sena" | |
let age; | |
try { | |
age = yield api2(); | |
console.log(age); | |
}catch (e) { | |
console.log("err:" + e); //err:api2 炸了 | |
} | |
return { | |
name, | |
age | |
}; | |
} | |
function api1() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
resolve("sena"); | |
}, 1000); | |
}) | |
} | |
function api2() { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
reject("api2炸了"); | |
}, 1000); | |
}) | |
} | |
run(requestData).then((res) => { | |
console.log(res); // { name: 'sena', age: undefined } | |
}, (err) => { | |
console.log("错误原因 : " + err); | |
}); |
好了,一个简单的仿 co 自动执行函数就写完了,如果有遗漏或错误,欢迎指出