js中yield实现同步原理

在js中需要将异步方法同步的时候, 经常使用的是asyncawait, 或者用Promise

偶然在dvajs中看到其使用yield迭代器实现了同步的效果, 例如

1
2
3
4
5
6
7
8
9
10
11
function* f(){
var a = Promise.resolve(1);
console.log(a); // Promise
var ra = yield a;
console.log(ra); // 1
var b = Promise.resolve(2);
console.log(b); // Promise
var rb = yield b;
console.log(rb); // 2
return "success";
}

当然直接运行不能得到预期的效果, Promise没用同步执行, yield返回的结果也是undefined, 因为还缺少对其的一层封装, 或者说还缺少个执行器

1
2
3
4
var it = f();
it.next();
it.next();
it.next();

传统的迭代器, 是这样使用的

1
2
3
4
5
6
7
8
9
function* range(){
for(let i=0;i<n;i++){
yield i;
}
}
var it = range();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2

如下封装, 在每一次yield返回的Promisethen中进行下一次迭代, 并把结果传入 g.next(r), 迭代函数的next(r)中的参数r会成为函数体中yield标识的表达式的返回值, 从而达到类似await的效果

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 async(g) {
var ctx = this;
return new Promise(function(resolve, reject) {
g = g.apply(ctx);
next();
function next(r) {
var result = g.next(r);
if (result.done){
return resolve(result.value);
}
result.value.then(next);
}
});
}

async(function*(){
var a = Promise.resolve(1);
console.log(a); // Promise
var ra = yield a;
console.log(ra); // 1
var b = Promise.resolve(2);
console.log(b); // Promise
var rb = yield b;
console.log(rb); // 2
return "success";
}).then(v=>{
console.log(v) // success
});