- 通过生成器让函数持续执行
- 使用 promise 处理异步任务
- 使用生成器和 promise 书写优雅代码
try {
var ninjas = syncGetJSON('ninjas.json');
var missions = syncGetJSON(ninjas[0].missionsUrl);
var missionDetails = syncGetJSON(missions[0].detailsUrl);
//Study the mission description
} catch (e) {
//Oh no, we weren't able to get the mission details
}
改为回调:
getJSON('ninjas.json', function(err, ninjas) {
if (err) {
console.log('Error fetching list of ninjas', err);
return;
}
getJSON(ninjas[0].missionsUrl, function(err, missions) {
if (err) {
console.log('Error locating ninja missions', err);
return;
}
getJSON(missions[0].detailsUrl, function(err, missionDetails) {
if (err) {
console.log('Error locating mission details', err);
return;
}
//Study the intel plan
});
});
});
生成器函数:
async(function*() {
try {
const ninjas = yield getJSON('ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);
//Study the mission details
} catch (e) {
//Oh no, we weren't able to get the mission details
}
});
生成器函数和标准函数非常不同。对初学者来说,调用生成器并不会执行生成器函数,相反,它会创建一个叫作迭代器(iterator)的对象。
调用生成器函数不一定会执行生成器函数体。通过创建迭代器对象,可以与生成器通信。
function* WeaponGenerator() {
yield 'Katana';
yield 'Wakizashi';
}
const weaponsIterator = WeaponGenerator();
const result1 = weaponsIterator.next();
console.log(
typeof result1 === 'object' && result1.value === 'Katana' && !result1.done
); // true
const result2 = weaponsIterator.next();
console.log(
typeof result2 === 'object' && result2.value === 'Wakizashi' && !result2.done
); // true
const result3 = weaponsIterator.next();
console.log(
typeof result3 === 'object' && result3.value === undefined && result3.done
); // true
- 调用生成器后,就会创建一个迭代器(iterator):
const weaponsIterator = WeaponGenerator();
- 迭代器用于控制生成器的执行。迭代器对象暴露的最基本接口是
next
方法。这个方 法可以用来向生成器请求一个值,从而控制生成器:const result1 = weaponsIterator.next();
next
函数调用后,生成器就开始执行代码,当代码执行到yield
关键字时,就会生成一个中间结果(生成值序列中的一项),然后返回一个新对象,其中封装了结果值和一个指示完成的指示器。- 每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值请求的到达。
对迭代器进行迭代
function* WeaponGenerator() {
yield 'Katana';
yield 'Wakizashi';
}
const weaponsIterator = WeaponGenerator();
let item;
while (!(item = weaponsIterator.next()).done) {
console.log(item !== null, item.value);
}
把执行权交给下一个生成器
function* WarriorGenerator() {
yield 'Sun Tzu';
yield* NinjaGenerator();
yield 'Genghis Khan';
}
function* NinjaGenerator() {
yield 'Hattori';
yield 'Yoshi';
}
for (let warrior of WarriorGenerator()) {
console.log(warrior !== null, warrior);
}
// true "Sun Tzu"
// true "Hattori"
// true "Yoshi"
// true "Genghis Khan"
用生成器生成 ID 序列
function* IdGenerator() {
let id = 0;
while (true) {
yield ++id;
}
}
const idIterator = IdGenerator();
const ninja1 = { id: idIterator.next().value };
const ninja2 = { id: idIterator.next().value };
const ninja3 = { id: idIterator.next().value };
console.log(ninja1.id === 1, 'First ninja has id 1');
console.log(ninja2.id === 2, 'Second ninja has id 2');
console.log(ninja3.id === 3, 'Third ninja has id 3');
// true "First ninja has id 1"
// true "Second ninja has id 2"
// true "Third ninja has id 3"
迭代器中包含一个局部变量 id,其代表了 ID 计数器。局部变量 id 仅能在该生成器中被访问,故而完全不必担心有人会不小心在代码的其他地方修改 id 值。
标准函数中一般不应该书写无限循环的代码。但在生成器中没问题!当生成器遇到了一 个
yield
语句,它就会一直挂起执行直到下次调用next
方法,所以只有每次调用一次next
方法,while
循环才会迭代一次并返回下一个ID
值。
使用迭代器遍历 DOM 树
递归遍历:
<div id="subTree">
<form>
<input type="text"/>
</form>
<p>Paragraph</p>
<span>Span</span>
</div>
<script>
function traverseDOM(element, callback) {
callback(element);
element = element.firstElementChild;
while (element) {
traverseDOM(element, callback);
element = element.nextElementSibling;
}
}
const subTree = document.getElementById("subTree");
traverseDOM(subTree, function(element) {
console.log(element !== null, element.nodeName);
});
</script>
生成器:
function* DomTraversal(element) {
yield element;
element = element.firstElementChild;
while (element) {
yield* DomTraversal(element);
element = element.nextElementSibling;
}
}
const subTree = document.getElementById('subTree');
for (let element of DomTraversal(subTree)) {
console.log(element !== null, element.nodeName);
}
我们已经知道了调用一个生成器不会实际执行它。相反,它创建了一个新的迭代器, 通过该迭代器我们才能从生成器中请求值。在生成器生成(或让渡)了一个值后,生成 器会挂起执行并等待下一个请求的到来。在某种方面来说,生成器的工作更像是一个小 程序,一个在状态中运动的状态机。
- 挂起开始——创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。
- 执行——生成器中的代码已执行。执行要么是刚开始,要么是从上次挂起的时候继续的。当生成器对应的迭代器调用了
next
方法,并且当前存在可执行的代码时,生成器都会转移到这个状态。 - 挂起让渡——当生成器在执行过程中遇到了一个
yield
表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。 - 完成——在生成器执行期间,如果代码执行到
return
语句或者全部代码执行完毕,生成器就进入该状态。
async(function*() {
try {
const ninjas = yield getJSON('data/ninjas.json');
const missions = yield getJSON(ninjas[0].missionsUrl);
const missionDescription = yield getJSON(missions[0].detailsUrl);
//Study the mission details
} catch (e) {
//Oh no, we weren't able to get the mission details
}
});
function async(generator) { // 定义一个辅助函数,用于对我们定义的生成器执行操作
var iterator = generator(); // 创建一个迭代器,进而我们可以控制生成器
function handle(iteratorResult) { // 定义函数 handle,用于对生成器产生的每个值进行处理
if (iteratorResult.done) { // 当生成器没有更多结果返回时停止执行
return;
}
const iteratorValue = iteratorResult.value;
if (iteratorValue instanceof Promise) { // 如果生成的值是一个promise,则对其注册成 功和失败回调。这是异步处理的部分。如果 promise成功返回,则恢复生成器的执行并传 入 promise 的返回结果。如果遇到错误,则向生成器抛出异常
iteratorValue
.then(res => handle(iterator.next(res)))
.catch(err => iterator.throw(err));
}
}
try {
handle(iterator.next()); // 重启生成器的执行
} catch (e) {
iterator.throw(e);
}
}
async 函数获取了一个生成器,调用它并创建了一个迭代器用来恢复生成器的执行。 在 async 函数内,我们声明了一个处理函数用于处理从生成器中返回的值——迭代器的 一次“迭代”。如果生成器的结果是一个被成功兑现的承诺,我们就是用迭代器的 next 方法把承诺的值返回给生成器并恢复执行。如果出现错误,承诺被违背,我们就使用迭 代器的 throw 方法(告诉过你迟早能派上用场了)抛出一个异常。直到生成器的工作完 成前,我们都会一直重复这几个操作。
这只是个粗略的草稿, 一个最小化的代码应该把生成器和 promise 结合在一起。不推荐 在生产环境下使用这种代码。
- 函数是第一类对象——我们向
async
函数传入了一个参数,该参数也是函数。 - 生成器函数——用它的特性来挂起和恢复执行。
- promise——帮我们处理异步代码。
- 回调函数——在 promise 对象上注册成功和失败的回调函数。
- 箭头函数——箭头函数的简洁适合用在回调函数上。
- 闭包——在我们控制生成器的过程中, 迭代器在
async
函数内被创建,随之我们在promise
的回调函数内通过闭包来获取该迭代器。
- 生成器是一种不会在同时输出所有值序列的函数,而是基于每次的请求生成值。
- 不同于标准函数,生成器可以挂起和回复它们的执行状态。当生成器生成了一个值后,它将会在不阻塞主线程的基础上挂起执行,随后静静地等待下次请求。
- 生成器通过在
function
后面加一个星号(*)来定义。在生成器函数体内,我们可以使用新的关键字yield
来生成一个值并挂起生成器的执行。如果我们想让渡到另一个生成器中,可以使用yield
操作符。 - 在我们控制生成器的执行过程中,通过使用迭代器的
next
方法调用一个生成器,它能够创建一个迭代器对象。除此之外,我们还能够通过next
函数向生成器中传入值。 promise
是计算结果值的一个占位符,它是对我们最终会得到异步计算结果的一个保证。promise
既可以成功也可以失败,一旦设定好了,就不能够有更多改变。promise
显著地简化了我们处理异步代码的过程。通过使用then
方法来生成promise
链,我们就能轻易地处理异步时序依赖。并行执行多个异步任务也同样简单:仅使用Promise.all
方法即可。- 通过将生成器和
promise
相结合我们能够使用同步代码来简化异步任务。