Appearance
异步遍历器
同步遍历器的问题
Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的 next 方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。next 方法返回的对象的结构是{value, done},其中 value 表示当前的数据的值,done 是一个布尔值,表示遍历是否结束。
js
function idMaker() {
let index = 0
return {
next: function () {
return { value: index++, done: false }
}
}
}
const it = idMaker()
it.next().value // 0
it.next().value // 1
it.next().value // 2
// ...隐含着一个规定,it.next()方法必须是同步的,只要调用就必须立刻返回值。不适合异步操作。
解决方法:将异步操作包装成 Thunk 函数或者 Promise 对象,即 next()方法返回值的 value 属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而 done 属性则还是同步产生的
异步操作包装为 Promise
js
function idMaker() {
let index = 0
return {
next: function () {
return {
value: new Promise((resolve) => setTimeout(() => resolve(index++), 1000)),
done: false
}
}
}
}
const it = idMaker()
it.next().value.then((o) => console.log(o)) // 0
it.next().value.then((o) => console.log(o)) // 1
it.next().value.then((o) => console.log(o)) // 2
// ...问题:语义不清晰,迭代器返回 Promise,还要进行 .then 操作
ES2018 引入了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即 value 和 done 这两个属性都是异步产生
异步遍历的接口
异步遍历器特点:调用遍历器的 next 方法,返回的是一个 Promise 对象。
异步遍历器接口,部署在 Symbol.asyncIterator 属性。Symbol.asyncIterator 属性有值,表示可以进行异步遍历
异步遍历器的例子
js
const asyncIterable = createAsyncIterable(['a', 'b'])
const asyncIterator = asyncIterable[Symbol.asyncIterator]()
asyncIterator
.next()
.then((iterResult1) => {
console.log(iterResult1) // { value: 'a', done: false }
return asyncIterator.next()
})
.then((iterResult2) => {
console.log(iterResult2) // { value: 'b', done: false }
return asyncIterator.next()
})
.then((iterResult3) => {
console.log(iterResult3) // { value: undefined, done: true }
})异步遍历器返回了两次值
- 第一次,返回 Promise
- 第二次,等待 Promise resolve,再返回
异步遍历器的 next 方法是可以连续调用的。
用法 1:所有的 next 方法放在 Promise.all 方法里面
js
const asyncIterable = createAsyncIterable(['a', 'b'])
const asyncIterator = asyncIterable[Symbol.asyncIterator]()
const [{ value: v1 }, { value: v2 }] = await Promise.all([asyncIterator.next(), asyncIterator.next()])
console.log(v1, v2) // a b用法 2:一次性调用所有的 next 方法,然后 await 最后一步操作
js
async function runner() {
const writer = openFile('someFile.txt')
writer.next('hello')
writer.next('world')
await writer.return()
}
runner()for await...of
新引入的 for await...of 循环,用于遍历异步的 Iterator 接口。
js
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x)
}
}
// a
// bfor...of 循环自动调用这个对象的异步遍历器的 next 方法,会得到一个 Promise 对象。await 用来处理这个 Promise 对象,一旦 resolve,就把得到的值(x)传入 for...of 的循环体
next 方法返回的 Promise 对象被 reject,for await...of 就会报错,要用 try...catch 捕捉
js
async function () {
try {
for await (const x of createRejectingIterable()) {
console.log(x);
}
} catch (e) {
console.error(e);
}
}for await...of 循环也可以用于同步遍历器
js
;(async function () {
for await (const x of ['a', 'b']) {
console.log(x)
}
})()
// a
// bNode v10 支持异步遍历器,Stream 就部署了这个接口
js
// 传统写法
function main(inputFilePath) {
const readStream = fs.createReadStream(inputFilePath, { encoding: 'utf8', highWaterMark: 1024 })
readStream.on('data', (chunk) => {
console.log('>>> ' + chunk)
})
readStream.on('end', () => {
console.log('### DONE ###')
})
}
// 异步遍历器写法
async function main(inputFilePath) {
const readStream = fs.createReadStream(inputFilePath, { encoding: 'utf8', highWaterMark: 1024 })
for await (const chunk of readStream) {
console.log('>>> ' + chunk)
}
console.log('### DONE ###')
}异步 Generator 函数
Generator 函数返回一个同步遍历器对象,异步 Generator 函数返回一个异步遍历器对象。
语法上:异步 Generator 函数就是async函数与 Generator 函数的结合
js
async function* gen() {
yield 'hello'
}
const genObj = gen()
genObj.next().then((x) => console.log(x))
// { value: 'hello', done: false }异步遍历器设计目之一:Generator 函数 用 一套接口处理同步和异步
js
// 同步 Generator 函数
function* map(iterable, func) {
const iter = iterable[Symbol.iterator]()
while (true) {
const { value, done } = iter.next()
if (done) break
yield func(value)
}
}
// 异步 Generator 函数
async function* map(iterable, func) {
const iter = iterable[Symbol.asyncIterator]()
while (true) {
const { value, done } = await iter.next()
if (done) break
yield func(value)
}
}另一个异步 Generator 函数的例子
js
async function* readLines(path) {
let file = await fileOpen(path)
try {
while (!file.EOF) {
yield await file.readLine()
}
} finally {
await file.close()
}
}await:返回 Promise 对象,将外部操作产生值输入函数内部。
yield:暂停 next 方法,将函数内布值输出。
异步 Generator 函数用法
js
;(async function () {
for await (const line of readLines(filePath)) {
console.log(line)
}
})()例子:
js
function fetchRandom() {
const url = 'https://www.random.org/decimal-fractions/' + '?num=1&dec=10&col=1&format=plain&rnd=new'
return fetch(url)
}
async function* asyncGenerator() {
console.log('Start')
const result = await fetchRandom() // (A)
yield 'Result: ' + (await result.text()) // (B)
console.log('Done')
}
const ag = asyncGenerator()
ag.next().then(({ value, done }) => {
console.log(value)
})yield命令后面是一个字符串,会被自动包装成一个 Promise 对象。
代码执行顺序
ag.next()立刻返回一个 Promise 对象。asyncGenerator函数开始执行,打印出Start。await命令返回一个 Promise 对象,asyncGenerator函数停在这里。- A 处变成 fulfilled 状态,产生的值放入
result变量,asyncGenerator函数继续往下执行。 - 函数在 B 处的
yield暂停执行,一旦yield命令取到值,ag.next()返回的那个 Promise 对象变成 fulfilled 状态。 ag.next()后面的then方法指定的回调函数开始执行。该回调函数的参数是一个对象{value, done},其中value的值是yield命令后面的那个表达式的值,done的值是false。
异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为 reject,然后抛出的错误被 catch 方法捕获
js
async function* asyncGenerator() {
throw new Error('Problem!')
}
asyncGenerator()
.next()
.catch((err) => console.log(err)) // Error: Problem!普通 async 函数返回 Promise 对象,异步 Generator 函数返回异步 Iterator 对象。async 函数 和 异步 Generator 函数,是封装异步操作的两种方法。前者自带执行器,后者通过 for await ... of 执行 或 自定义执行器
js
async function takeAsync(asyncIterable, count = Infinity) {
const result = []
const iterator = asyncIterable[Symbol.asyncIterator]()
while (result.length < count) {
const { value, done } = await iterator.next()
if (done) break
result.push(value)
}
return result
}自动执行器使用实例
js
async function f() {
async function* gen() {
yield 'a'
yield 'b'
yield 'c'
}
return await takeAsync(gen())
}
f().then(function (result) {
console.log(result) // ['a', 'b', 'c']
})异步 Generator 函数以后,JavaScript 就有四种函数形式:普通函数、async 函数、Generator 函数和异步 Generator 函数
作用区分
async 函数:一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘)
异步 Generator:一系列产生相同数据结构的异步操作(比如一行一行读取文件)
异步 Generator 函数可以通过 next 方法参数,接收外部传入的数据
js
const writer = openFile('someFile.txt')
writer.next('hello') // 立即执行
writer.next('world') // 立即执行
await writer.return() // 等待写入结束代码分析:通过 next 向函数内部传参,每次 next 同步执行,通过 await 等待整个写入操作。
同步的数据结构,也可以使用异步 Generator 函数
js
async function* createAsyncIterable(syncIterable) {
for (const elem of syncIterable) {
yield elem
}
}yield* 语句
yield*语句处理异步遍历器
js
async function* gen1() {
yield 'a'
yield 'b'
return 2
}
async function* gen2() {
const result = yield* gen1()
console.log('result ', result)
}与同步 Generator 函数一样,for await...of循环会展开yield*
js
;(async function () {
for await (const x of gen2()) {
console.log(x)
}
})()
// result 2
// a
// b