《JavaScript忍者秘籍(第二版)》学习笔记
💡 生成器函数的主要用途是什么?在异步代码中,为什么使用promise比使用简单的回调函数更好?
使用Promise.race来执行很多长期执行的任务时,promise最终会在什么时候变成resolved状态?
它什么时候会无法变成resolved状态?
6.2 生成器函数
调用生成器并不会执行生成器函数,它会创建一个叫作迭代器(iterator)的对象。
function* WeaponGenerator() {
yield 'one'
yield 'two'
yield 'three'
}
for (let weapon of WeaponGenerator()) {
console.log(weapon)
}
// one
// two
// three
6.2.1 通过迭代器对象控制生成器
const interator = WeaponGenerator()
interator.next() // {value: 'one', done: false}
interator.next() // {value: 'two', done: false}
interator.next() // {value: 'three', done: false}
interator.next() // {value: undefined, done: true}
let item
while(!(item = interator.next()).done) {
item.value && console.log(item.value)
}
把执行权交给下一个生成器
function * W() {
yield 'a'
yield * N()
yield 'd'
}
function * N() {
yield 'b'
yield 'c'
}
for(let w of W()) {
console.log(w)
}
// a b c d
6.2.2 使用生成器
-
用生成器生成ID序列
function * IdGenerator() { let id = 0 while(true) { yield ++id } }标准函数中一般不应该书写无限循环的代码。但在生成器中没问题!当生成器遇到了一个yield语句,它就会一直挂起执行直到下次调用next方法,所以只有每次调用一次next方法,while循环才会迭代一次并返回下一个ID值。
-
使用迭代器遍历DOM树
function * DomTraversal(el) { yield el; el = el.firstElementChild while(el) { yield * DomTraversal(el) el = el.nextElementSibling } } const subTree = document.getElementById('subTree') for (let element of DomTraversal(subTree)) { element !== null && console.log(element.nodeName) }
6.2.3 与生成器交互
可以往next()中传值
如果将参数传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。
在生成器的代码路径中的yield运算符,以及通过将其传递给Generator.prototype.next()指定新的起始值的能力之间,生成器提供了强大的控制力。
function * Gen(name) {
let imp = yield('Hello ' + name)
yield imp
}
const Iterator = Gen('World') // 第一次调用没有记录任何内容,因为生成器最初没有产生任何结果。
console.log(Iterator.next()) // {value: 'Hello World', done: false}
console.log(Iterator.next('Debra')) // {value: 'Debra', done: false}
// next()传了'Debra', 会成为yield('Hello ' + name)的返回值,即yield('Hello ' + name) === 'Debra'
6.2.4 探索生成器内部构成
- 挂起开始——创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。
- 执行——生成器中的代码已执行。执行要么是刚开始,要么是从上次挂起的时候继续的。当生成器对应的迭代器调用了next方法,并且当前存在可执行的代码时,生成器都会转移到这个状态。
- 挂起让渡——当生成器在执行过程中遇到了一个yield表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
- 完成——在生成器执行期间,如果代码执行到return语句或者全部代码执行完毕,生成器就进入该状态。
6.3 使用Promise
const promiseDelay = new Promise((resolve, reject) => {
console.log('promise is delay')
setTimeout(() => {
console.log('do then')
resolve('one')
}, 1000)
})
promiseDelay.then(res => {
console.log(res, 'is done')
})
只有执行了了resolve, Pomise才到达fulfilled状态
6.3.5 链式使用Promise
同时返回 success 和 success again
const promiseDelay = new Promise((resolve, reject) => setTimeout(resolve, 1000, 'success'))
promiseDelay.then(res => {
console.log(res)
return 'success again'
}).then(res => {
console.log(res)
})
间隔1秒 返回success 和 success again
const promiseDelay = new Promise((resolve, reject) => setTimeout(resolve, 1000, 'success'))
promiseDelay.then(res => {
console.log(res)
return new Promise((resolve, reject) => setTimeout(resolve, 1000, 'success'))
}).then(res => {
console.log(res)
})
// 第一个then的return不能直接return promiseDelay,因为这个promise已经是fulfill状态了,会直接执行下一个then
6.3.6 等待多个promise
全部成功后才进入then,只要有一个失败就直接进入catch
const promiseDelay1 = new Promise((resolve, reject) => setTimeout(resolve, 1000, '1 success'))
const promiseDelay2 = new Promise((resolve, reject) => setTimeout(resolve, 2000, '2 success'))
const promiseDelay3 = new Promise((resolve, reject) => setTimeout(resolve, 500, '3 success'))
Promise.all([promiseDelay1, promiseDelay2, promiseDelay3])
.then(([r1, r2, r3]) => {
console.log(r1, r2, r3)
})
// 2秒后返回 => 1 success 2 success 3 success
// 只要有一个fail
const promiseDelay1 = new Promise((resolve, reject) => setTimeout(resolve, 1000, '1 success'))
const promiseDelay2 = new Promise((resolve, reject) => setTimeout(resolve, 2000, '2 success'))
const promiseDelay3 = new Promise((resolve, reject) => setTimeout(reject, 500, '3 fail'))
Promise.all([promiseDelay1, promiseDelay2, promiseDelay3])
.then(([r1, r2, r3]) => {
console.log(r1, r2, r3)
}).catch(e => {
console.log(e) // 500毫秒后 返回 这个fail
})
6.3.7 promise竞赛
只执行最快返回的结果,无论fail还是success
const promiseDelay1 = new Promise((resolve, reject) => setTimeout(reject, 1000, '1 fail'))
const promiseDelay2 = new Promise((resolve, reject) => setTimeout(resolve, 2000, '2 success'))
const promiseDelay3 = new Promise((resolve, reject) => setTimeout(resolve, 500, '3 success'))
Promise.race([promiseDelay1, promiseDelay2, promiseDelay3])
.then((result) => {
console.log(result) // 3 success
}).catch(e => {
console.log(e)
})
6.4 生成器 + promise
(下面这个例子我也不大明白意义在哪里)
const promiseDelay1 = new Promise((resolve, reject) => setTimeout(reject, 1000, '1 fail'))
const promiseDelay2 = new Promise((resolve, reject) => setTimeout(resolve, 2000, '2 success'))
const promiseDelay3 = new Promise((resolve, reject) => setTimeout(resolve, 500, '3 success'))
const iteratorFn = function* () {
try {
yield promiseDelay1
yield promiseDelay2
yield promiseDelay3
} catch (e) {
console.log(e)
}
}
function asyncFn(generator) {
const iterator = generator()
function handle(result) {
if (result.done) return
const iteratorVal = result.value
if (iteratorVal instanceof Promise) {
iteratorVal.then(res => {
handle(iterator.next(res))
}).catch(e => iterator.throw(e)) // fail后面就不会再执行了
}
}
try {
handle(iterator.next())
}
catch (e) { iterator.throw(e) }
}
asyncFn(iteratorFn)
async
通过在关键字function之前使用关键字async,可以表明当前的函数依赖一个异步返回的值。在每个调用异步任务的位置上,都要放置一个await关键字,用来告诉JS引擎,请在不阻塞应用执行的情况下在这个位置上等待执行结果。
async function asyncFn() {
try {
const pms1 = await promiseDelay1
const pms2 = await promiseDelay2
const pms3 = await promiseDelay3
console.log(pms1)
console.log(pms2)
console.log(pms3)
// 全部处理完毕后同时返回
} catch(e) {
console.log('Error:', e) // 如果有错误,会依次返回错误(不会执行后面正确的了)
}
}
asyncFn()
小结
- 生成器是一种不会在同时输出所有值序列的函数,而是基于每次的请求生成值。
- 不同于标准函数,生成器可以挂起和回复它们的执行状态。当生成器生成了一个值后,它将会在不阻塞主线程的基础上挂起执行,随后静静地等待下次请求。
- 生成器通过在function后面加一个星号(*)来定义。在生成器函数体内,我们可以使用新的关键字yield来生成一个值并挂起生成器的执行。如果我们想让渡到另一个生成器中,可以使用yield操作符。
- 在我们控制生成器的执行过程中,通过使用迭代器的next方法调用一个生成器,它能够创建一个迭代器对象。除此之外,我们还能够通过next函数向生成器中传入值。
- promise是计算结果值的一个占位符,它是对我们最终会得到异步计算结果的一个保证。promise既可以成功也可以失败,一旦设定好了,就不能够有更多改变。
- promise显著地简化了我们处理异步代码的过程。通过使用then方法来生成promise链,我们就能轻易地处理异步时序依赖。并行执行多个异步任务也同样简单:仅使用Promise.all方法即可。
- 通过将生成器和promise相结合我们能够使用同步代码来简化异步任务。
练习
function * EvenGenerator() {
let num = 2
while (true) {
yield num
num += 2
}
}
let generator = EvenGenerator()
let a1 = generator.next().value // 2
let a2 = generator.next().value // 4
let a3 = EvenGenerator().next().value // 2
let a4 = generator.next().value // 6
function * NinjaGenerator() {
yield 'ninja1'
yield 'ninja2'
yield 'ninja3'
}
const ninjas = []
for (let ninja of NinjaGenerator()) {
ninjas.push(ninja)
}
console.log(ninjas) // ['ninja1', 'ninja2', 'ninja3']
// 用while实现for of
**const ninjas = []
const generator = NinjaGenerator()
let ninja = generator.next()
while(!ninja.done) {
ninjas.push(ninja.value)
ninja = generator.next()
}
//** 如果NinjaGenerator里面有return, 后面的(包括return这一句)都不再执行
-
向next()传参
除了可以作为函数的中间返回语句使用,yield关键字还可以作为函数的中间参数使用。上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值。
这里有个地方不太好理解——第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数。
function * Gen(val) { val = yield val *2 yield val } let generator = Gen(2) let a1 = generator.next(3).value // 4 这里传的参数3会被忽略,因为这次的调用是为了开始执行生成器函数 let a2 = generator.next(5).value // 5 这个参数5作为yield val * 2的返回值,然后赋值给valfunction *Gen(val) { val = yield val + 2 yield val yield val } let generator = Gen(2) console.log(generator.next()) // 4 第一次传不传参都一样 console.log(generator.next(9)) // 9 这里9会作为yield val + 2的返回值,赋值给val console.log(generator.next(5)) // 9 这里传5没有赋值给val,val还是9function *Gen(val) { val = yield val + 2 val = yield val yield val } let generator = Gen(2) console.log(generator.next()) // 4 console.log(generator.next(9)) // 9 console.log(generator.next(5)) // 5
const promise = new Promise((resolve, reject) => {
resolve(1) // Promise状态已经完成,不会再调用reject了
setTimeout(() => reject(2), 500)
})
promise
.then(val => console.log('Success:', val)) // 只会输出Success: 1
.catch(e => console.log('Error:', e)) // 不会被调用到