迭代器,生成器(generator)和Promise的“微妙”關係

小美娜娜發表於2018-08-28

需要Promise原始碼版的朋友:傳送連結

本文主要講述(iterator)和生成器*/yield之間的聯絡和各自的用法,以及生成器的高配版本async/await的使用。

大綱:

  • 迭代器(iterator)
  • 生成器 */yield
  • 非同步版生成器 async/await

迭代器(iterator)

先瞅瞅“迭代”,這個詞是什麼意思呢?每一次“過程”的重複,稱之為迭代。不過迭代是會保留結果的,也就說每次都是以上一次迭代的結果為基準,開始下一次的迭代。舉個例子,迭代這個詞經常出現在產品開發之中,每個週期都會有產品的迭代開發,但是不可能每次都是從零開始做產品,肯定是基於上一版本的產品進行開發,也就是進行迭代。

從中我們可以整理出關於迭代的兩個關鍵點:

  • 過程是重複的
  • 返回上一次的迭代結果

那麼JS中的“迭代器”是個怎樣的概念呢?

檢視MDN中的概念:傳送地址

個人觀點:JS中的迭代器,就是一個陣列物件,不斷地呼叫next重複獲取過程,然後每次都返回一個結果。等到沒有東西可返回了,就終止。因此next的返回物件有兩個屬性donevaluedone表示是否結束了,value表示當前迭代的結果。當donetrue的時候,表示迭代已結束,這時候是沒有返回結果的也就是沒有value這個屬性。

然而迭代器是有一系列的規範的:

檢視MDN中的概念:傳送地址

迭代器

  • 關於迭代器,就是我們上面討論的next方法,返回donevaluedone:true時可以省略)兩個引數。
function iteratorFunc(){
    let arr=[...arguments]
    let nIndex=0
    return {
        next:()=>{
            return nIndex<arr.length?
            {value:arr[nIndex++],done:false}:{done:true}
        }
    }
}
let a=iteratorFunc(1,2,3)
console.log(a.next())//{done:false,value:1}
console.log(a.next())//{done:false,value:2}
console.log(a.next())//{done:false,value:3}
console.log(a.next())//{done:true}
複製程式碼

可迭代“物件”

  • 關於可迭代“物件”,我們需要再物件上實現@@iterator方法,也就是[Symbol.iterator],返回一個自定義的迭代方法,以表明這個物件是可以迭代的。有些JS內建的物件就是可迭代的,比如String,Array。

自帶的可迭代事例:

let str="我是歡樂的迭代器"
let b=str[Symbol.iterator]()
console.log(b.next())//{value: "我", done: false}
console.log(b.next())//{value: "是", done: false}
console.log(b.next())//{value: "歡", done: false}
複製程式碼

有沒有很神奇啊!用了這麼久的字串,居然還有這種操作。他的效果等同於上方的自定義迭代方法。那麼我們來寫個自定義的迭代方法:

str[Symbol.iterator] = function() {
    return { // this is the iterator object, returning a single element, the string "bye"
      next: function() {
        this._index += 2
        if (this._index<str.length) {
          return { value: str[this._index], done: false };
        } else {
          return { done: true };
        }
      },
      _index:-2
    };
};
let c=str[Symbol.iterator]()
console.log(c.next())//{value: "我", done: false}
console.log(c.next())//{value: "歡", done: false}
console.log(c.next())//{value: "的", done: false}
console.log(c.next())//{value: "代", done: false}
console.log(c.next())//{done: true}
複製程式碼

這裡我寫的迭代器是返回一個隔一個字元。執行成功~yeah~

生成器(generator)

感覺寫迭代器還是很繞呢,於是出現了生成器(generator),專門幫我們生成迭代器的存在。

function * g(){}
let it= g()
console.log(it.next())//{value: undefined, done: true}
複製程式碼

看到熟悉的結構沒有!{value: undefined, done: true},不過我們沒有值。這個時候要向大家推薦*的好基友yield,一個yield對應一個next的值。

我們改寫下上方的字串的迭代器:

str[Symbol.iterator]= function * (){
    let index=-2;
    while(index<this.length){
        index += 2
        yield this[index]
    }
}
let kk=str[Symbol.iterator]()
console.log(kk.next())//{value: "我", done: false}
console.log(kk.next())//{value: "歡", done: false}
console.log(kk.next())//{value: "的", done: false}
console.log(kk.next())//{value: "代", done: false}
複製程式碼

是不是方便了很多。

我們帶著幾個疑問來看看生成器:

  • yield的返回值是啥?
  • 執行順序?

例項程式碼:

function * gy(){
    console.log("zero")
    let fisrt=yield "first"
    console.log("fisrt",fisrt)
    let second=yield "first"
    console.log("second",second)
}
let ity= gy()
複製程式碼

第一次執行ity.next(),只列印了zero

第二次執行ity.next(),只列印了first undefined

第三次執行ity.next("third"),只列印了second third

由此可見每次的next都止步於yield,就不再執行下去了。yield每次返回的都是當前ity.next(value)value值。

async/await

我們來看看對於Promise這個物件的迭代器,我們該怎麼處理。也就是每個迭代器都是非同步的。

function setTime(value,id){
    return new Promise((r,j)=>setTimeout(() => {
        console.log(value)
        r(id)
    }, 10))
}
function *a(){
    let r1 = yield setTime("first",1)
    console.log(r1)
    let r2 =yield setTime("second",2)
    console.log(r2)
    let r3 =yield setTime("third",3)
    console.log(r3)
}
let k=a();
new Promise((resolve,reject)=>{
    function next(data){
        let {value,done}=k.next(data)
        //k.next()返回一個promise,因此可以then
        if(!done){
            value.then((data)=>{
                console.log(data)
                next(data)
            })
        }
    }
    next();
})
複製程式碼

因為每個都是非同步的,所以需要我們二次處理,這個時候async/await就可以出場了。只需要把*/yield無縫改成async/await即可。

async function a() {
    let r1 = await setTime("first",1)
    console.log(r1)
    let r2 = await setTime("second",2)
    console.log(r2)
    let r3 = await setTime("third",3)
    console.log(r3)
}
a()
複製程式碼

相關文章