ES6學習筆記(六)【promise,Generator】

風靈使發表於2019-04-05

簡介

在ES6以前,我們處理非同步操作只能使用回撥函式

ES6中新增了幾種書寫非同步程式碼的解決方案,promise是最常用的一種

正文

Promise是一個建構函式,我們可以用new關鍵字生成一個promise例項來使用

let promise = new Promise((resolve, reject) => {
        //做一些非同步操作
        setTimeout(() => {
            console.log('success')
            resolve('promise is success')
        }, 2000)
    })

Promise建構函式接受一個函式作為引數,在這個函式內執行一些非同步操作

這個函式擁有兩個引數:resolve和reject,這兩個都是函式,js引擎會幫你傳入

在函式內部呼叫他們的時候分別代表對外宣告非同步操作已經成功(resolve)或失敗(reject)

為什麼會設計的這樣複雜?

setTimeout(() => {
  return 1
}, 2000)

這是一個簡單的回撥方式處理非同步操作的結果,但是回撥函式被外層的定時器包裹

我們沒法簡單的拿到回撥函式的返回值,這也是回撥函式最大的缺陷

所以promise通過resolve和reject方法,對外傳遞一個可以輕易訪問到資訊

如果我們需要拿到並處理promise內部resolve的資訊,需要使用then方法:

let promise = new Promise((resolve, reject) => {
        //做一些非同步操作
        setTimeout(() => {
            console.log('success')
            resolve('promise is success')
        }, 2000)
    })

promise.then(x => console.log(x))
// promise is success

promise例項上的then方法是promise裡最常用的方法,它接受一個函式為引數,這個函式的第一個引數就是這個promise內部resolve出來的值,從而我們可以在這個函式內部獲取和使用這個值

另一個常用方法是catch,如果這個promise非同步操作出了問題

我們會在函式內部呼叫reject方法傳遞出去錯誤資訊,代表promise出錯了

這時候應該使用catch方法來處理錯誤資訊:

let promise = new Promise((resolve, reject) => {
        //做一些非同步操作
        setTimeout(() => {
            console.log('error')
            reject('promise is error')
        }, 2000)
    })

promise.then(x => console.log(x))
// 報錯 Uncaught (in promise) promise is success

promise.catch(x => console.log(x)
// 不報錯 輸出 promise is error

其實如果你給then方法傳入兩個函式,那麼第二個函式也是可以捕獲到這個錯誤的

catch方法只是then的一個別名,不過為了程式碼清晰易讀

我們最好都是用catch,而不是給then傳入兩個引數

現在我們有then方法處理執行成功的promise,用catch處理出錯的promise

顯然需要一種無論成功還是出錯都能處理的方法,就像try…catch中的finally

promise也提供了一個finally方法,用法與then,catch完全相同

無論promise成功還是失敗都會執行finally中的回撥函式

除了這四種promise例項上的方法以外,js還原生提供了Promise.resolve()和Promise.reject()方法

注意不是promise內部的resolve,reject方法,不要混淆

這兩個方法可以簡單的把一個非同步操作包裝成promise:

Promise.resolve('success')
// 等價於
new Promise((resolve, reject) => resolve('success'))

Promise.reject('error')
// 等價於
new Promise((resolve, reject) => reject('error'))

如果需要快速建立一個resolve或者reject狀態的promise,就用這兩個方法

promise建構函式也可以包裝多個promise例項,來讓一些非同步操作並行執行

let all = Promise.all([p1, p2, p3])

let race = Promise.race([p1, p2, p3])

Promise.all和Promise.race方法都接收一個可遍歷物件為引數,其中每一項都是一個promise例項

上面程式碼中 p1, p2, p3 都是一個promise例項,內部要執行一些非同步操作

在Promise.all生成的promise物件中,p1, p2, p3都完成(resolve)的時候,

它自身才算完成,自動呼叫內部的resolve方法

而Promise.race生成的promise的物件中.只要p1, p2, p3有一個完成

它就算完成,立刻呼叫resolve方法

但是還沒完成的也不會終止,最終三個promise也都會完成

思考

這部分內容希望你都可以手動敲一遍,獨立思考

嘗試用Promise封裝一個發GET請求的方法,接受一個url字串為引數

返回一個請求該URL的Promise,並且狀態碼為200時可以呼叫then方法獲取返回資料

狀態碼為其他時可以在catch中輸出報錯資訊


根據上面寫出的請求封裝函式生成三個請求不同url的Promise

並用Promise.race嘗試做一個延遲處理,一秒內沒有收到響應的請求就不再獲取資料


Generator 是ES6新增的一種函式,可以看做一個狀態機

通常我們通過Generator函式來實現一些非同步操作

正文

定義一個Generator函式需要在function關鍵字後加一個*號

function *f() {
  yield 1
  yield 2
}
let a = f()
a.next()      // {value: 1, done: false}
a.next()      // {value: 2, done: false}
a.next()      // {value: undefined, done: true}
a.next()      // {value: undefined, done: true}

顯然我們定義個一個叫做f的Generator函式,那麼f可以看做是一個狀態機

f的內部我們用 yield 關鍵字定義了兩個 狀態

然後我們通過f()執行這個函式,與普通函式不同的是,Generator函式執行後,

得到的結果並不是函式的返回值,而是而是一個代表其內部狀態的Iterator

Iterator我們之前已經介紹過了,是一個通用的遍歷器介面,我們可以呼叫它上面的next方法得到其內部的值

yield 命令用在Generator函式體中,與return類似,停止執行函式並把它後面的表示式的值返回

yield 把Generator函式體劃分成一塊一塊來執行,每次呼叫next方法,都會使函式繼續向下執行,直到遇見下一個 yield 並把值放在value裡返回

如果後續再沒有其他 yield 或return 就把done欄位置為true,代表全部的狀態都已執行完畢

正常情況下我們不會手動去一次次呼叫next檢視結果,而是使用 for...of 迴圈:

function *f() {
  yield 1
  yield 2
}
for(let x of f()){
  console.log(x)
}
// 1 2

關於Generator的細節知識點很多,比如next還可以傳入引數影響Generator函式內部狀態等等

不過時至今日(17年6月),Generator在開發中的應用已經很少了,這裡不再為新手介紹其他知識

主流的非同步方案已經逐漸成型,目前我們使用Promise和Async來解決非同步問題

思考

這部分內容希望你都可以手動敲一遍,獨立思考

嘗試寫出一個表示斐波那契數列的Generator函式

函式接收一個引數,代表生成的斐波那契數列的長度

function* fib(length){
  // 做一些操作,然後使用yield返回每一步的結果
}
for(let x of fib(7)){
  console.log(x)
}
// 輸出 [1, 1, 2, 3, 5, 8, 13]

相關文章