ES6之Promise再解讀

做者:小火柴@毛豆前端前端

ES6中的Promise對象是異步編程的一個重要的點,下面是我整理的學習筆記跟你們分享一下,在這以前我以爲有必要先了解一下JS事件機制和一些相關的異步操做。git

JS事件機制

在說JS事件機制以前我們先提一嘴瀏覽器進程。 瀏覽器是多進程運行的,JS引擎只是瀏覽器渲染進程中的一個單線程,在單線程中一次只能執行一個任務,多任務處理的狀況下就要進行排隊等候順序執行,所以會出現若某任務執行很耗時,等待時間過長而卡死的狀況,爲了解決因爲單線程特性出現的"卡死"問題,就用到了我們今天要說的JS異步。 下圖是瀏覽器進程思惟導圖: github

1.png-581.9kB

JS引擎遇到一個異步事件後並不會一直等待其返回結果,而是將這個事件掛起,繼續執行執行棧中的其餘任務。當一個異步事件執行完畢並返回結果後,JS會將這個事件加入與當前執行棧不一樣的另外一個隊列--事件隊列。被放入事件隊列不會當即執行其回調,而是等待當前執行棧的全部任務執行完畢,主線程處於閒置狀態時,主線程回去查找事件隊列是否有任務。若是有,那麼主線程會取出排在第一位的事件,並把該事件對應的回調放到執行棧中,而後執行其中的同步代碼...,如此反覆,這就造成了一個無線循環,也就是咱們說的事件循環(Event Loop) 下圖就是JS事件機制說明: 面試

image.png-83.4kB

宏任務 & 微任務

事件循環過程是一個宏觀的表述,因爲異步任務之間並不相同,其執行優先級也有區別。所以不一樣的異步任務被分爲宏任務和微任務兩類。 編程

image.png-41kB

運行機制: 1.執行一個宏任務(棧中沒有就從事件隊列中獲取) 2.執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中 3.宏任務執行完畢後,當即執行當前微任務隊列中的全部微任務(依次執行) 4.當前宏任務執行完畢,開始檢查渲染,而後GUI線程接管渲染 5.渲染完畢後,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取)數組

經典面試題:promise

setTimeout(() => { console.log(4); }, 0);
new Promise(resolve => {
    console.log(1);
    resolve()
    console.log(2)
}).then(() => {
    console.log(5)
})
console.log(3)
// 結果:1 2 3 5 4
複製代碼

Promise其實就是一個異步的微任務,那咱們就開啓Promise之旅吧。瀏覽器

Promise

簡介bash

Promise 是異步編程的一種解決方案,比傳統的解決方案 (回調函數和事件)更合理和更強大。簡單說它就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise操做後返回的對象仍是一個新的Promise對象,因此支持鏈式調用,它能夠把異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數,更便於理解與閱讀。異步

Promise主要有如下特色

  1. Promise對象有不受外界影響的三個狀態:

pending(進行中) fulfilled(已成功) rejected(已失敗) 只有異步操做的結果才能肯定當前處於哪一種狀態,任何其餘操做都不能改變這個狀態。這也是Promise(承諾)的由來。

  1. Promise狀態一旦改變就不會再變,任什麼時候候均可以獲得這個結果。它的狀態改變只有兩種結果:

pending ----> fulfilled pending ----> rejected 只要有其中一種狀況發生,狀態就凝固了,不會再變,會一直獲得這個結果,後續再添加Promise的回調函數也只能拿到前面狀態凝固的結果

Promise缺點:

1.沒法取消Promise,一旦新建它就會當即執行,沒法中途取消

2.若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部

3.當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)

Promise API

從圖中也能夠看出來,Promise既是一個對象也是一個構造函數

image.png-35.5kB

Promise基本用法

resolve / reject

let promise = new Promise((resolve, reject) => {
    if(/**操做成功 */){
        resolve(seccess)
    }else{
        reject(error)
    }
})
複製代碼

Promise接收一個函數做爲參數,函數裏有resolve和reject兩個參數,這兩個參數實際上是Promise內置的兩個方法,會在異步操做執行結束後調用,能夠將異步操做的結果回傳至回調函數,以肯定Promise最終的一個狀態(是fulfilled仍是rejected)。 resolve方法的做用是將Promise的pending狀態變爲fulfilled,在異步操做成功以後調用,能夠將異步返回的結果做爲參數傳遞出去。 resolve還能夠接受Promise實例做爲參數

let p1 = new Promise((resolve, reject) => {
    reject('error')
})
let p2 = new Promise((resolve, reject) => {
    resolve(p1)
})
p2.then(s => console.log(s))
    .catch(e => console.log(e))
複製代碼

reject方法的做用是將Promise的pending狀態變爲rejected,在異步操做失敗以後調用,能夠將異步返回的結果做爲參數傳遞出去。 ⚠️他們之間只能有一個被執行,不會同時被執行,由於Promise只能保持一種狀態。

then()

Promise實例肯定後,能夠用then方法分別指定fulfilled狀態和rejected狀態的回調函數。

let promise = new Pormise();
promise.then(success => {
    // 等同於上面的resolve(success)
}, error => {
    // 注意:此處沒法捕獲onfulfilled拋出的錯誤
})
複製代碼

then(onfulfilled,onrejected)方法中有兩個參數,兩個參數都是函數,第一個參數執行的是resolve()方法(即異步成功後的回調方法),第二參數執行的是reject()方法(即異步失敗後的回調方法)(第二個參數可選)。它返回的是一個新的Promise對象,所以能夠採用鏈式寫法(解決了異步串行的操做,避免了傳統異步串行操做層層嵌套的問題)。

function createPromise(p, state){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(state === 0){
                reject(`error, ${p}`)
            }else{
                resolve(`success, ${p}`)
            }
        }, 0)
    })
}
createPromise('p1', 1).then(success => {
    console.log('111', success)
    return createPromise('p2', 2)
}).then(success => {
    console.log('222', success)
    return createPromise('p3', 3)
}).then(success => {
    console.log('333', success)
})

// 111 success, p1
// 222 success, p2
// 333 success, p3
複製代碼

⚠️then 方法注意點:簡便的 Promise 鏈式編程最好保持扁平化,不要嵌套 Promise。

catch()

catch方法其實是.then(null,onrejected)的別名,用於指定發生錯誤時的回調函數。做用和then中的onrejected同樣,它還能夠捕獲onfulfilled拋出的錯,彌補了then的第二個回調onrejected的缺陷

new Promise().then(success => {
    // ...
}).catch(error => {
    // ...
})
複製代碼

⚠️注意:串行操做時只能捕獲前面Promise拋出的錯,而沒法捕獲在他們後面的Promise拋出的錯

createPromise('p1', 0).then(success => {
    console.log('111', success)
    return createPromise('p2', 0)
}).then(success => {
    console.log('222', success)
    return createPromise('p3', 0)
}).catch(error => {
    console.log('333', error)
})
// 333 error, p1
複製代碼

finally()

finally方法用於指定無論Promise對象最後狀態如何,都會執行的操做。 該方法不接受任何參數,因此跟Promise的狀態無關,不依賴於Promise的執行結果

createPromise('p1', 1).then(success => {
    console.log('111', success)
}).catch(error => {
    console.log('222', error)
}).finally(() => {
    console.log('finally')
})
// 111 success, p1
// finally
複製代碼

all()

Promise.all方法接受一個以Promise實例組成的數組做爲參數。Promise的all方法提供了並行執行異步操做的能力,而且在全部異步操做都執行完畢後才執行回調,只要其中一個異步操做返回的狀態爲rejected那麼Promise.all()返回的Promise即爲rejected狀態,此時第一個被reject的實例的返回值,會傳遞給Promise.all的回調函數:

Promise
.all([createPromise('p1', 1), createPromise('p2', 1)])
.then(r => { console.log(r) })
// ["success, p1", "success, p2"]
Promise
.all([createPromise('p1', 1), createPromise('p2', 0)])
.then(r => { console.log(r) })
.catch(e => { console.log(e) })
// error, p2
複製代碼

若Promise.all的Promise實例參數本身定義了catch方法且被rejected,就不會觸發Promise.all()的catch方法了,而是執行了then

let p2 = createPromise('p2', 0).catch(e => {
    console.log('p2-catch', e)
})
Promise
    .all([createPromise('p1', 1), p2])
    .then(r => { console.log(r) })
    .catch(e => { console.log(e) })
    // p2-catch error, p2
    // ["success, p1", undefined]
複製代碼

race()

Promise的race方法和all方法相似,區別在於all方法的效果其實是(誰慢以誰爲準),而race方法則是(誰快以誰爲準)

Promise
.race([createPromise('p1', 1), createPromise('p2', 0)])
.then(r => { console.log(r) })
.catch(e => { console.log(e) })
// success, p1
複製代碼
相關文章
相關標籤/搜索