Promise實用理解

1.Promise含義

Promise 是異步編程的一種解決方案優於傳統的解決方案——回調函數和事件。 簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。 Promise對象表明一個異步操做,有三種狀態:pending(進行中)、resolved(已成功)和rejected(已失敗)。es6

2.Promise執行順序

setTimeout(() => {
    console.log('a');
}, 0);
let p = new Promise((resolve, reject) => {
    console.log('b');
    resolve();
});
p.then(() => {
    console.log('d');
});
console.log('c');

// 控制檯輸出:
// 'b'
// 'c'
// 'd'
// 'a'

複製代碼

要理解該輸出順序首先應該瞭解js的執行任務隊列優先級(由高到低)編程

  • 主線程
  • Micro-task隊列 (微任務)
  • Macro-tasks隊列 (宏任務)

首先setTimeout屬於宏任務扔進Macro-tasks隊列,新建實例Promise時接受一個回調函數做爲參數,注意此時該回調函數屬於主線程會馬上執行,輸出'b'緊接着執行resolve也就意味着該promise對象的狀態將從pending更新爲resolved,其掛載的回調函數也就是then裏面的參數函數並不會當即執行,由於它屬於微任務,因此丟進Micro-task隊列。接下來輸出'c',到目前爲止主線程任務已經結束,接着執行微任務輸出'd',最後執行宏任務輸出'a'數組

3.Promise狀態更新

let p1 = new Promise(function (resolve, reject) { 
    resolve('p1');
});
let p2 = new Promise(function (resolve, reject) {
    setTimeout(() => {
        resolve('p2')
    }, 100);
});
let p3 = new Promise(function (resolve, reject) {
    setTimeout(() => {
        reject('p3')
        resolve('p3')
    }, 100);
});

p1.then((value) => {
    console.log(value);
})
p2.then((value) => {
    console.log(value);
})
p3.then((value) => {
    console.log('success', value);
}, (value) => {
    console.log('error', value);
})
console.log('p1:', p1);
console.log('p2:', p2);
console.log('p3:', p3);

setTimeout(() => {
    console.log('p1:', p1);
    console.log('p2:', p2);
    console.log('p3:', p3);
}, 100);

// 控制檯輸出
// p1: Promise {[[resolved]]: "p1"}
// p2: Promise {[[pending]]}
// p3: Promise {[[pending]]}
// p1
// p2
// error p3
// p1: Promise {[[resolved]]: "p1"}
// p2: Promise {[[resolved]]: "p2"}
// p3: Promise {[[rejected]]: "p3"}
複製代碼

p1最新建立就調用了resolve則它的狀態馬上變爲resolved,值爲p1,但此時p2和p3都爲pending狀態,100毫秒後p2輸出值p2且狀態轉爲resolved。 p3首先調用了reject則其狀態轉爲rejected,值爲p3,儘管下一行又調用了resolve但並無任何做用忽略成功的回調,只有error p3。 這段實驗也顯示出Promise的一個特色promise

  • 調用then方法傳入回調能夠從外部接受promise的異步返回數據value,當嵌套多級異步操做時這種優點更大。
  • 狀態的不可逆性,Promise的狀態和值肯定下來,後續再調用resolve或reject方法,不能改變它的狀態和值。

3.Promise之then實例方法

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('a');
    }, 1000);
}).then(function (value) {               
    console.log("第一個" + value);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(value + 'b');
        }, 1000);
    })
}).then(function (value) {              
    console.log("第二個" + value);
}).then(function (value) {
    console.log("第三個" + value);
    console.log(a);
}).then(function (value) {              
    console.log("第四個" + value);
}, (err) => {
    console.log("第四個error", err);
})

// 第一個a
// 第二個ab
// 第三個undefined
// 第四個error ReferenceError: a is not defined
複製代碼

then方法是Promise的實例方法,調用then後的返回值依然是一個promise對象,注意它是全新的promise對象,通常能夠看到then的鏈式調用,這裏須要注意區別於jQuery的鏈式調用。jQuery是返回調用對象自己。當鏈式調用時要注意不能被它繞暈了,要抓住一個重點,咱們只是在調用then方法而已,給它傳參只是定義函數,並無執行!何時執行?是根據你的異步操做後的promise狀態如何更新以及什麼時候更新而肯定。 傳給then的回調函數中的返回值影響着最終返回出的promise對象,參數的返回值通常有三種狀況。dom

  • 一個普通的同步值,或者沒寫返回值默認就是undefined,固然它也屬於普通同步值。則then最終返回的是狀態是resolve成功的Promise對象,如上段代碼的第三個輸出,它的前一個then方法內部沒有返回值則默認undefined,接下來就直接走進第三個then方法,且值value就是undefined
  • 返回新的Promise對象,then方法將根據這個Promise的狀態和值建立一個新的Promise對象返回。如第二個輸出,會等待上個then方法返回的新Promise對象狀態的更新來肯定,且會等待它的更新以及將最後的值傳過來,這種狀況也是當有多級異步操做所使用的方式。
  • throw一個同步異常,then方法將返回一個rejected狀態的Promise, 值是該異常。如第四個輸出!

4.Promise之catch實例方法

Promise.prototype.catch方法是then(null, rejection)的別名,用於指定發生錯誤時的回調函數。異步

let p = new Promise((resolve, reject) => {
    //
});
p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同於
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));
複製代碼

catch方法,它首先是捕捉處理錯誤,不管是promise調用了reject方法仍是直接拋出錯誤,都會走到catch方法內進行處理。接下來就和then方法同樣,返回的也是一個全新的Promise對象,錯誤處理的回調函數返回值一樣有三種狀況,具體看上個then方法。異步編程

let p = new Promise((resolve, reject) => {
    reject('失敗')
});
p.then((val) => console.log('1then: success', val))
 .then((val) => console.log('2then: success', val))
 .catch((val) => console.log('3catch: error', val))
 .catch((val) => console.log('4catch: error', val))
 .then((val) => console.log('5then: success', val))

// 控制檯輸出

// 3catch: error 失敗
// 5then: success undefined
複製代碼

Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。 上段代碼首先p這個Promise對象(狀態是resolved)遇到第一個then會忽略掉它定義的成功回調,注意此時調用完第一個then方法後的返回值是全新的Promise對象!且狀態一樣是resolved,爲什麼會這樣?由於它把p的狀態進行了一層包裝也就做爲了本身的狀態,且值也和它同樣!因此說Promise的狀態具備傳遞性。函數

由於這個錯誤目前並無被捕獲處理,因此繼續向後傳遞。一樣遇到第二個then時咱們能夠當作跳過它,但發生的細節和第一個then同理,直到3catch將這個錯誤捕獲,因此輸出3catch: error 失敗。上面也提到catch也就是then的一個別名而已,本質其實差很少。故此時catch調用後的返回值再次是一個全新的promise對象,那狀態呢?由於這邊給catch傳遞的參數並無定義返回值,因此默認就是一個同步值undefined,則catch返回的promise對象的狀態就是resolved。那麼它調用最後一個then輸出5then: success undefined,也就不難理解了。post

5.Promise之resolve、reject靜態方法

let p1 = Promise.resolve('p1');
p1.then(val => console.log('success', val), val => console.log('error', val))

let p2 = Promise.reject('p2');
p2.then(val => console.log('success', val), val => console.log('error', val))
複製代碼

當傳入參數是通常同步值時則返回一個狀態爲resolve或reject的Promise對象,值也就是傳入的參數,相應的會調用成功或失敗的回調。ui

let p1 = Promise.resolve(1);
let p2 = Promise.resolve(p1);
let p3 = new Promise(function (resolve, reject) {
    resolve(p1);
});

console.log(p1 === p2)
console.log(p1 === p3)

p1.then((value) => { console.log('p1=' + value)})
p2.then((value) => { console.log('p2=' + value)})
p3.then((value) => { console.log('p3=' + value)})

// 控制檯輸出:
// true
// false
// p1=1
// p2=1
// p3=1
複製代碼

當傳入一個Promise對象時,則resolve就直接返回該Promise對象,故p1 === p2true,p3則爲全新的Promise對象,可是它狀態馬上變爲resolve且值爲p1,它會獲取p1的狀態和值做爲本身的值。故p3=1

6.Promise之all、race靜態方法

function timeout(who) {
    return new Promise(function (resolve, reject) {
        let wait = Math.ceil(Math.random() * 3) * 1000;
        setTimeout(function () {
            if (Math.random() > 0.5) {
                resolve(who + ' inner success');
            }
            else {
                reject(who + ' inner error');
            }
        }, wait);
        console.log(who, 'wait:', wait);
    });
}

let p1 = timeout('p1');
let p2 = timeout('p2');

p1.then((success) => { console.log(success) }).catch((error) => { console.log(error) })
p2.then((success) => { console.log(success) }).catch((error) => { console.log(error) })

// race只要有一個狀態改變那就當即觸發且決定總體狀態失敗仍是成功.
// all只要有一個失敗那就當即觸發總體失敗了,兩個都成功總體才成功.
Promise.all([p1, p2])
    .then((...args) => {
        console.log('all success', args)
    })
    .catch((...args) => {
        console.log('someone error', args)
    })

// 控制檯輸出(狀況1)
// p1 wait: 3000
// p2 wait: 1000p2 inner error
// someone error [ 'p2 inner error' ]
// p1 inner success

// 控制檯輸出(狀況2)
// p1 wait: 2000
// p2 wait: 2000
// p1 inner success
// p2 inner success
// all success [ [ 'p1 inner success', 'p2 inner success' ] ]

複製代碼

all、race方法接受數組做爲參數,且數組每一個成員都爲Promise對象。若是不是的話就調用Promise.resolve方法,將其轉爲 Promise 實例,再進一步處理。使用表示要包裝的多個promise異步操做來肯定。具體能夠看代碼理解,要多動手本身試驗!

若有錯誤或疑問歡迎指正留言:

參考文獻:

阮一峯ECMAScript 6 入門 Promise

八段代碼完全掌握 Promise

相關文章
相關標籤/搜索