1. 程式人生 > >ES9中的非同步迭代器(Async iterator)和非同步生成器(Async generator)

ES9中的非同步迭代器(Async iterator)和非同步生成器(Async generator)

ES9新增了非同步迭代器(Async iterator)非同步執行語句(for...await...of)非同步生成器(Async generator),本文帶領大家瞭解這三個新特性,以及如何建立非同步迭代器。

1. 迭代器(Iterator)

如果你還不瞭解ES6的迭代器,也就是iterator,先來看看這一部分。

iterator是一個特殊的物件,它包含一個next方法,next方法返回一個物件,這個物件包含兩個屬性,一個是value,表示成員的值,一個是donedone的值是一個布林型別,表示迭代器是否結束。

iterator.next() // 返回 {value: '', done: false}
複製程式碼

迭代器內部會儲存一個指標,指向迭代器的成員位置,每呼叫一次next方法,指標就會移動到下一個成員,直到指標指向迭代器最後一個成員後面的位置,這時,done的值為truevalue的值一般為undefined,需要根據iterator的實際實現來決定。

1.1 建立iterator

實現一個函式,用來建立iterator

幾個關鍵點

  • iterator是一個物件,並且含有一個next方法
  • next方法返回一個物件,包含一個value屬性和done屬性,value表示返回的值,done是一個布林型別,表示迭代器是否結束
  • iterator內部包含一個內部指標,指向迭代器的成員的位置,每呼叫一次next
    方法,指標就會移動到下一個成員,直到指標指向迭代器最後一個成員後面的位置,這時done的值為true
// 可以通過傳入陣列或者物件建立iterator
const createIterator = items => {
    const keys = Object.keys(items)
    const len = keys.length
    let pointer = 0
    return {
        next() {
            const done = pointer >= len
            const value = !done ? items[keys[pointer++]] : undefined
return { value, done } } } } 複製程式碼
const iterator1 = createIterator([1, 2, 3])
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: undefined, done: true }
複製程式碼
const iterator2 = createIterator({a: 'a', b: 'b', c: 'c'})
iterator.next() // { value: 'a', done: false }
iterator.next() // { value: 'b', done: false }
iterator.next() // { value: 'c', done: false }
iterator.next() // { value: undefined, done: true }
複製程式碼

1.2 iterator和for...of

部署了iterator介面的資料結構,也就是具有Symbol.iterator方法的資料結構,就可以被for...of遍歷。Symbol.iterator方法類似於上面實現的createIterator函式

  • 陣列原生具有iterator介面
const arr = [1, 2, 3]
typeof arr[Symbol.iterator] // 'function'

for (const val of arr) {
    console.log(val)
}
// 1
// 2
// 3
複製程式碼
  • 物件預設沒有iterator介面,但是我們可以自己部署
const obj = {a: 'a', b: 'b', c: 'c'}
typeof obj[Symbol.iterator] // 'undefined'

for (const val of obj) {
    console.log(val)
}
// TypeError: obj is not iterable
複製程式碼

給物件部署iterator介面

const obj = {a: 'a', b: 'b', c: 'c'}
obj[Symbol.iterator] = function() {
    const self = this
    const keys = Object.keys(self)
    const len = keys.length
    let pointer = 0
    return {
        next() {
            const done = pointer >= len
            const value = !done ? self[keys[pointer++]] : undefined
            return {
                value,
                done
            }
        }
    }
}
for (const val of obj) {
    console.log(val)
}
// a
// b
// c
複製程式碼

2. 生成器(Generator)

Generator是一個特殊的函式,函式體內部使用yield表示式,定義不同的內部狀態,當執行Generator函式時,不會直接執行函式體,而是會返回一個遍歷器物件(iterator)。

  • Generator函式內部可以使用yield表示式,定義內部狀態
  • function關鍵字與函式名之間有一個*
function* generator() {
    console.log('start');
    yield 1
    yield 2
    yield 3
    console.log('end')
}
const iterator = generator() // 這時函式體並沒有被執行,而是建立了一個iterator
// 當呼叫iterator的next方法時,函式體開始執行,
iterator.next() // 'start'  {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // 'end'   {value: undefined, done: true}
複製程式碼
  • 每呼叫一次next方法,函式體會從函式頭部或上次停下來的地方開始執行,直到遇到下一個yield表示式或者return語句時停止
  • yield表示式後面的值會作為next方法返回的物件的value屬性值
  • return會作為iterator結束的標記,並且return的值會作為next方法返回的物件的value屬性值

改寫一下上面的例子

function* generator() {
    yield 1
    yield 2
    return 3
}
const iterator = generator()
// 當呼叫iterator的next方法時,函式體開始執行,
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: true}
複製程式碼

Generator函式生成的iterator可以被for...of遍歷

function* generator() {
    yield 1
    yield 2
    yield 3
}

const iterator = generator()
typeof iterator[Symbol.iterator] // 'function'

for (const val of iterator) {
    console.log(val)
}
// 1
// 2
// 3
複製程式碼

在這裡我們只需要知道Generator函式會生成一個iterator就夠了,但實際上Generator函式遠不止這些,這裡我們不做詳細介紹了,感興趣的同學可以看看阮一峰Generator教程

3. 非同步迭代器(Asynchronous Iterator)

ES9新增了非同步迭代器

非同步迭代器同步迭代器相同,都是一個函式,並且含有一個next方法,區別在於同步迭代器next方法返回一個含有valuedone屬性的物件,而非同步迭代器next方法返回一個Promise物件,並且Promise物件的值為含有valuedone屬性的物件。

// 這是一個非同步迭代器
asyncIterator.next().then(res => {
    console.log(res.value, res.done)
})
複製程式碼

我們來實現一個建立非同步迭代器的方法

const createAsyncIterator = items => {
    const keys = Object.keys(items)
    const len = keys.length
    let pointer = 0
    return {
        next() {
            const done = pointer >= len
            const value = !done ? items[keys[pointer++]] : undefined
            return Promise.resolve({
                value,
                done
            })
        }
    }
}
複製程式碼

同步迭代器相同,每呼叫一次next方法,非同步迭代器內部的指標就移動到下一個成員

const aynscIterator = createAsyncIterator([1, 2, 3])
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 1 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 2 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // 3 false
})
aynscIterator.next().then(({value, done}) => {
    console.log(value, done) // undefined true
})
複製程式碼

3.1 for...await...of

for...of方法能夠遍歷具有Symbol.iterator介面的同步迭代器資料,但是不能遍歷非同步迭代器。 ES9新增的for...await...of可以用來遍歷具有Symbol.asyncIterator方法的資料結構,也就是非同步迭代器,且會等待前一個成員的狀態改變後才會遍歷到下一個成員,相當於async函式內部的await

定義一個具有Symbol.asyncIterator方法的物件

const asyncItems = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.asyncIterator]() {
        const items = this
        const keys = Object.keys(items)
        const len = keys.length
        let pointer = 0
        return {
            next() {
                const done = pointer >= len
                const value = !done ? items[keys[pointer++]] : undefined;
                return new Promise((resolve) => {
                    setTimeout(() => {
                        resolve({value, done})
                    }, 1000)
                }) 
            }
        }
    }
}
複製程式碼

使用for...await...of遍歷該物件

// await只能用在async函式中
async function run() {
    for await (const value of asyncItems) {
        console.log(value);
    }
}
run();
// 1s後打印出 1
// 再過1s後打印出 2
// 再過1s後打印出 3
複製程式碼

上面的例子實現了每隔1s打印出物件的屬性值的非同步遍歷器介面,可以看到, 當使用for...await..of,遍歷時,會等待前一個Promise物件的狀態改變後,再遍歷到下一個成員。

3.2 非同步生成器(Async Generator)

我們可以採取一種更方便的方式建立非同步迭代器,就是利用非同步生成器

非同步生成器和普通的生成器很像,但是其是async函式,內部可以使用await表示式,並且它返回一個具有Symbol.asyncIterator方法的物件。

定義一個非同步生成器

async function* asyncGenerator() {
    yield await Promise.resolve(1);
    yield await Promise.resolve(2);
    yield await Promise.resolve(3);
}
複製程式碼

使用for...await...of遍歷該物件

const asyncIterator = asyncGenerator()
typeof asyncIterator[Symbol.asyncIterator] // 'function'
async function run() {
    for await (const value of asyncIterator) {
        console.log(value);
    }
}
run();
// 1
// 2
// 3
複製程式碼

4. 總結

  • 非同步迭代器同步迭代器相同的是,非同步迭代器也是一個具有next方法的物件
  • 非同步迭代器物件的next方法返回一個Promise物件Promise物件的值為一個物件,包含一個value屬性和一個done屬性
  • for...await...of可以遍歷具有Symbol.asyncIterator方法的資料結構,並且會等待上一個成員狀態改變後再繼續執行
  • 非同步生成器(Async Generator)可以用來建立非同步迭代器,它是一個async型別的generator函式,內部可以使用await表示式等待非同步方法的執行完成,並使用for...await...of遍歷