JS 异步编程之二:理解生成器(Generator)
作者:nunumick 发布时间:25 Jul 2017 分类: front-end
上篇学习了迭代器的应用原理,我们知道可以用生成器(generator)来返回迭代方法,简化对象迭代器的实现。这篇我们学习生成器的相关知识点。
先来看下官方定义:
The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol. Generator is a subclass of the hidden Iterator class.
可知 Generator 对象是迭代器的子类,同样遵循迭代协议。其作用就是用于生成迭代方法。
虽然自定义迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此创建时要格外谨慎。生成器函数(Generator 函数)提供了一个强大的替代选择:它允许你定义一个非连续执行的函数作为迭代算法。生成器函数使用 function* 语法编写。
//生成器函数内部每个yield语句定义了生成器暂停执行并返回值给调用者的一个点。
//当生成器恢复执行时,它会从暂停处继续,保持其内部状态不变。
function* geA() {
yield "a1";
yield "a2";
yield "a3";
yield "a4";
}
//显式调用geA()返回的迭代器对象
//自动反复调用其.next()方法,直到迭代器耗尽(即.next().done为true)
for (let item of geA()) {
console.log(item);
}
/**
* output:
a1
a2
a3
a4
*/
异步编程
JS 的异步函数并不会阻塞代码执行顺序,想要通过按照异步函数编写的顺序来实现同步的执行结果并不容易。
//生成器返回迭代器
let ge = geA();
/*
* 在js中,延时方法因为异步特性,这样写并不会得到期待的
* a1
* sleep 1s
* sleep function A done
* a2
* a3
* 结果
*/
console.log(ge.next().value); //a1
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep 1s");
resolve("sleep function A done");
}, 1000);
});
promise.then((res) => {
console.log(res);
console.log(ge.next().value); //a3
});
console.log(ge.next().value); //a2
/*
* 我们需要这样串联起 promise 函数
*/
console.log(ge.next().value); //a1
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep 1s");
resolve("sleep function A done");
}, 1000);
});
promise.then((res) => {
console.log(res);
return ge.next().value; //a2
}).then((res)=>{
console.log(res);
console.log(ge.next().value); //a3
})
yield & next()
生成器可以通过 yield 语句来暂停执行,并在得到调用 next() 方法时继续,我们可以给 next 方法传递参数作为 yield 语句参与运算的值。这样便可以形成一个运算控制链。
可以思考下面这段代码的执行结果。
//探究generator yield & next
const geB = function* (v) {
let v1, v2, v3, v4, v5, v6, v7, v8;
v1 = yield v + 1;
console.log(v1);
v2 = yield v1 + 2; //这一行可以拿上一个yield的返回值做运算,但需要next()传递进来
console.log(v2);
v3 = yield v2 + 3;
console.log(v3); //v3赋值成功
console.log(v4); //v4未定义
yield v4 + 4;
console.log(v4);
v8 = yield v + v1 + v2 + v3 + v4 + v5 + v6 + v7;
console.log(v, v1, v2, v3, v4, v5, v6, v7, v8);
yield v + v8;
yield* geA();
};
let geb = geB(100);
console.log(geb.next(1)); //第一个yield返回值传递没有作用,此时 v1 为 undefined,console.log(v1)未执行
console.log(geb.next(2)); //往下执行第一个yield后的代码,v1赋值,并参与 +2 运算
console.log(geb.next(10)); //可以随意修改赋值
console.log(geb.next(1)); //由此可看出,将yield表达式赋值给变量,next() -> yield 语句运行 -> next(yield result) 可形成闭环
console.log(geb.next(1, 2, 3, 4)); //next()只支持一个参数,且必定为上一yield表达式的返回值
console.log(geb.next(400)); //500
console.log(geb.next());
console.log(geb.next());
console.log(geb.next());
首次调用:geb.next(1) 开始执行生成器。首先,v + 1 计算得到 101 作为第一个 yield 表达式的返回值。由于这是生成器的第一次执行,此时并没有接收任何外部传入的值来更新 v1,因此 v1 仍然为 undefined,不会打印 console.log(v1)。 输出:{ value: 101, done: false }
第二次调用:geb.next(2) 继续执行生成器。由于上一步已经返回 101,这次调用传入的 2 被赋给 v1,接着执行 console.log(v1) 打印 v1 的值,即 2。 yield 表达式:计算 v1 + 2 得到 4,作为第二个 yield 表达式的返回值。 输出:控制台输出 2(来自console.log(v1))和 { value: 4, done: false }
第三次调用:geb.next(10) 继续执行生成器。v2 被赋值为传入的 10,接着打印 v2 的值,即 10。 yield 表达式:计算 v2 + 3 得到 13,作为第三个 yield 表达式的返回值。 输出:控制台输出 10(来自console.log(v2))和 { value: 13, done: false }
第四次调用:geb.next(1) 继续执行生成器。v3 被赋值为传入的 1,接着打印 v3 的值,即 1。 打印未定义的 v4:此时尝试打印未定义的 v4,输出 undefined。 yield表达式:尽管 v4 未定义,但仍尝试计算 undefined + 4,得到 NaN。 输出:控制台依次输出 1、undefined 和 {value: NaN, done: false}
由此可见 next() 方法返回 yield 语句的执行结果,可以为下一次 yield 语句传递值,进而形成一个可控循环。
利用 generator 特性支持同步编程
可以利用 yield 和 next() 的链式特性,将多个 promise 串联起来使用。形如
promise.then.then.then.then… 这种写法可以转变为
p1 = yield p0.then;
p2 = yield p1.then;
p3 = yield p2.then;
来看看实际的例子:
let [s1, s2, s3, s4] = [2000, 500, 0, 100];
const promiseA = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep 2s");
resolve("done");
}, s1);
});
};
const promiseB = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep 0.5s");
resolve("done");
}, s2);
});
};
const promiseC = () => {
return new Promise((resolve, reject) => {
resolve("done");
});
};
const promiseD = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("sleep for a while");
resolve("done");
}, s4);
});
};
/*
* 编写一个首尾相接,同步执行的生成器函数
* 正确执行顺序和结果:
* 1. sleep 2s, a1
* 2. sleep 0.5s, a2
* 3. a3
* 4. sleep for a while, a4
* 总延时在2600左右
*/
const promiseGen = function* (start) {
const newGeA = geA();
console.log("------ promise generator start ------");
let timeStart;
let step1 = yield start.then(() => {
timeStart = +new Date();
return promiseA().then(() => {
return newGeA.next().value;
});
});
let step2 = yield step1.then(() => {
return promiseB().then(() => {
return newGeA.next().value;
});
});
let step3 = yield step2.then(() => {
return promiseC().then(() => {
return newGeA.next().value;
});
});
let step4 = yield step3.then(() => {
return promiseD().then(() => {
let timeEnd = +new Date();
console.log(s1 + s2 + s3 + s4, timeEnd - timeStart);
return newGeA.next().value;
});
});
let step5 = yield step4.then(() => {
console.log("------ promise generator end ------");
console.log("------ async demo start ------");
asyncDemo();
return new Promise((res, rej) => {
setTimeout(() => {
res("async done");
console.log("------ async demo end ------");
}, 3000);
});
});
let step6 = yield step5.then(() => {
syncDemo();
});
return step6;
};
//手动调用next()一步步执行生成器
function runGenerator(){
//获得生成器的迭代器
let newGen = promiseGen(Promise.resolve("start"));
//同步写法,本质上通过yield与next建立了链式反应
let p1 = newGen.next().value.then((res) => {
console.log("gen:", res);
});
let p2 = newGen.next(p1).value.then((res) => {
console.log("gen:", res);
});
let p3 = newGen.next(p2).value.then((res) => {
console.log("gen:", res);
});
let p4 = newGen.next(p3).value.then((res) => {
console.log("gen:", res);
});
let p5 = newGen.next(p4).value;
let p6 = newGen.next(p5);
console.log(newGen.next(100086)); //先打印出结果了,{value: 100086, done: true}
}
//对照组A
//同步的写法,异步执行,顺序是乱的
function asyncDemo() {
const newGe = geA();
promiseA().then((res) => {
console.log("async:", newGe.next().value);
});
promiseB().then((res) => {
console.log("async:", newGe.next().value);
});
promiseC().then((res) => {
console.log("async:", newGe.next().value);
});
promiseD().then((res) => {
console.log("async:", newGe.next().value);
});
}
//对照组B
//为了保证执行顺序,链式写法
function syncDemo() {
const newGe = geA();
console.log("------ sync demo start ------");
promiseA().then((res) => {
console.log("sync:", newGe.next().value);
promiseB().then((res) => {
console.log("sync:", newGe.next().value);
promiseC().then((res) => {
console.log("sync:", newGe.next().value);
promiseD().then((res) => {
console.log("sync:", newGe.next().value);
console.log("------ sync demo end ------");
});
});
});
});
}
//执行
runGenerator();
控制台输出结果:
------ promise generator start ------
{value: 100086, done: true}
sleep 2s
gen: a1
sleep 0.5s
gen: a2
gen: a3
sleep for a while
2600 2608
gen: a4
------ promise generator end ------
------ async demo start ------
async: a1
sleep for a while
async: a2
sleep 0.5s
async: a3
sleep 2s
async: a4
------ async demo end ------
------ sync demo start ------
sleep 2s
sync: a1
sleep 0.5s
sync: a2
sync: a3
sleep for a while
sync: a4
------ sync demo end ------
标签:
javascript
,
ecma
,
es6
,
generator
<<< EOF