Generator是ES6推出的新的语法。Generator通过协程实现,具有执行时暂停并交出执行权,之后又从暂停处恢复执行的特性。这使得Generator可以用于处理异步逻辑。但Generator本身并没有自执行功能,所以通常会搭配类似于co这种执行器一起使用。
在Generator发布不久, async/await也随之发布,它相当于是自带执行器的Generator,且语法相对更语义化,所以比Generator更为常用。
本节就探究一下,不借助async/await,如何实现Generaor自执行器,使之能达到和async/await差不多的效果。
以下面代码为例:
function* foo() {
const a = yield 1;
const b = yield 2;
const c = yield 3;
return 4;
}
对于同步代码来说,要让其自动执行非常简单:
function run(fn) {
const it = fn();
let result = it.next();
while (!result.done) {
result = it.next(result.value);
}
return result.value;
}
const result = run(foo);
console.log(result); // 4
但这种简单的处理方式无法处理异步逻辑,为了处理异步逻辑,我们需要:
- 自执行函数需要返回一个Promise对象,用于获取Generator最终结果
- 每次调用
next
后,将返回的结果转换为Promise对象,并且在该Promise完成后,在其then
方法中继续调用next
方法直至Generator完成为止
按照这个思路,先写点便于理解的代码:
function mockFetch(data, timeout, fail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// fail参数是为了模拟异步请求出错的情况
if (fail) {
reject("error");
}
resolve(data);
}, timeout);
});
}
function* bar() {
const a = yield mockFetch("a", 100);
const b = yield mockFetch("b", 100);
const c = yield mockFetch("c", 100);
return [a, b, c];
}
const it = bar();
const res1 = it.next();
res1.value.then((a) => {
const res2 = it.next(a);
res2.value.then((b) => {
const res3 = it.next(b);
res3.value.then((c) => {
const res4 = it.next(c);
console.log("result", res4); // result { value: [ 'a', 'b', 'c' ], done: true }
});
});
});
通过上述的代码,就已经实现了一个最基础的自执行器,但这种方法只适用于bar
函数。
下面开始真正的实现满足需要的自执行函数。
首先创建一个run
函数,它接受Generator函数和Generator函数的参数作为参数,并且返回一个Promise对象:
function run(fn, ...args) {
return new Promise((resolve, reject) => {
});
}
在执行Generator函数之前需要做一些边界判断:
function run(fn, ...args) {
return new Promise((resolve, reject) => {
let it;
// fn必须是一个函数
if (typeof fn === "function") {
it = fn(...args);
}
// fn返回的对象必须是一个迭代器
if (!it || typeof it.next !== "function") {
return resolve(it);
}
});
}
接下来就是重头戏了:
function run(fn, ...args) {
return new Promise((resolve, reject) => {
/* ...... */
onResolved();
function onResolved(value) {
let res = it.next(value);
next(res);
}
function next(res) {
if (res.done) {
return resolve(res.value);
}
// 将结果转换为Promise,然后进行统一处理
Promise.resolve(res.value).then(onResolved);
}
});
}
现在调用bar
函数就变得相当简单了:
run(bar).then((data) => {
console.log(data); // [1, 2, 3]
});
接下来进行异步操作的错误处理,在async/await中,当异步操作出错时,可以使用try/catch
捕获并进行错误处理:
async function baz() {
try {
const a = await mockFetch("a", 100, true);
} catch (error) {
// 错误处理
}
}
为了向Generator函数中传递Promise reject时的异常,需要用到throw
方法:
function run(fn, ...args) {
return new Promise((resolve, reject) => {
/* ...... */
onResolved();
function onResolved(value) {
let res;
// 如果Generator内未捕获异常,则会向外抛出,所以需要外部也进行异常处理
try {
res = it.next(value);
} catch (error) {
return reject(error);
}
next(res);
}
function onRejected(reason) {
let res;
try {
// 将reject的reason传递给Generator函数处理
res = it.throw(reason);
} catch (error) {
return reject(error);
}
next(res);
}
function next(res) {
if (res.done) {
return resolve(res.value);
}
// 将结果转换为Promise,然后进行统一处理
Promise.resolve(res.value).then(onResolved, onRejected);
}
});
}
写点代码测试一下:
// 内部未进行错误处理
function* foo() {
const a = yield mockFetch("data", 1000, "error");
}
// 内部有进行错误处理
function* bar() {
const a = yield mockFetch("data", 1000);
let b;
try {
b = yield mockFetch("data", 1000, "error");
} catch (error) {
console.log("error happened");
}
return [a, b];
}
run(foo).catch((error) => {
console.log("foo error", error); // foo error error
});
run(bar).then((data) => {
console.log("bar data", data); // bar data ["data", undefined]
});
至此我们就完成了一个简化版的co,完整代码如下:
function run(fn, ...args) {
return new Promise((resolve, reject) => {
let it;
// fn必须是一个函数
if (typeof fn === "function") {
it = fn(...args);
}
// fn返回的对象必须是一个迭代器
if (!it || typeof it.next !== "function") {
return resolve(it);
}
onResolved();
function onResolved(value) {
let res;
try {
res = it.next(value);
} catch (error) {
return reject(error);
}
next(res);
}
function onRejected(reason) {
let res;
try {
res = it.throw(reason);
} catch (error) {
return reject(error);
}
next(res);
}
function next(res) {
if (res.done) {
return resolve(res.value);
}
// 将结果转换为Promise,然后进行统一处理
Promise.resolve(res.value).then(onResolved, onRejected);
}
});
}
如果对本文有什么意见和建议,欢迎讨论和指正!