写在前面

最近发现了一个叫co的神奇东西,似乎是es7没发布时,没有async,await时的一个非常好用的库,感觉挺好玩的,打算模拟实现一个。

实现过程

只能执行同步代码版

这一版的代码十分简单,主要做到三点就行了

  • 把返回的promise的resolve函数一层层往下传,当迭代结束时,把生成器函数的返回值传给resolve函数
  • 如果没有结束迭代,就把上一次迭代获得的值作为下一次next函数执行的参数传入,一直重复执行直到迭代结束为止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
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()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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炸了
});

还是测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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自动执行函数就写完了,如果有遗漏或错误,欢迎指出