1.Promise含義
Promise 是非同步程式設計的一種解決方案優於傳統的解決方案——回撥函式和事件。 簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。 Promise物件代表一個非同步操作,有三種狀態:
pending
(進行中)、resolved
(已成功)和rejected
(已失敗)。
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的一個特點
- 呼叫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物件,引數的返回值一般有三種情況。
- 一個普通的同步值,或者沒寫返回值預設就是
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
,也就不難理解了。
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物件,值也就是傳入的引數,相應的會呼叫成功或失敗的回撥。
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 === p2
為true
,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非同步操作來確定。具體可以看程式碼理解,要多動手自己試驗!
如有錯誤或疑問歡迎指正留言: