前言:當前進階進度
看一下進階時間線:
看到夢想之前,我只想到了無限長的距離……
首先解釋一下這張時間線,它的起點就是入門完成,所以自然會從印象最深的概念開始入手——即異步這裏。所以較複雜的知識已經在之前搞完了,現在就是比較簡單的了。
本文將簡單的介紹一下Promise
以及promisify
的方法,不涉及底層源碼細則,最多從**Promise A+**規範角度進行解讀。
Yoshi, 開始吧。
回調地獄
在認識Promise
之前,有必要知道什麼是 回調地獄(Callback Hell),它還有一個顯得很霸氣卻極爲令所有JS開發者感到無比厭煩的名字—— 厄運金字塔(Pyramid of doom)。之所以先了解這些,這是因爲Promise
本來就是爲了避免在多重異步操作時產生深層次的嵌套回調提出的一種新型異步解決方案。
老樣子,來個栗子
function load(src,cb){
let es = document.createElement('script')
es.src = src
document.body.append(es)
es.onload=()=>{cb()}
es.onerror=()=>{cb(new Error('failed to load '+src )) }
}
load('./t1.js', err=>{
if(err===undefined) {
f1(); // t1內部的函數
} else {
console.log(err.message);
}
})
上面的代碼是很很常見的需求,隨便添加一個js
,然後執行裏面的內容。
但是也會需要加載多個腳本,同時執行一些函數:
load('./t1.js', err=>{
if(err===undefined) {
f1(); // t1內部的函數
load('./t2.js', err=>{
if(err===undefined) {
f2(); // t2內部的函數
load('./t3.js', err=>{
if(err===undefined) {
f3(); // t3內部的函數
} else {
console.log(err.message);
}
})
} else {
console.log(err.message);
}
})
} else {
console.log(err.message);
}
})
這樣的多重操作,會隨着嵌套層數變深逐漸失控,於是便形成了臭名昭著的厄運金字塔。爲了解決這個問題,於是Promise
便應運而生——主要是將深層回調變成了鏈式調用,這樣能更符合人類邏輯:
第一步:先讓load
返回一個Promise
:
// 每個 load 返回一個 Promise 對象
// 每個Promise對象都有一個 then 方法。
function load(src){
let es = document.createElement('script')
es.src = src
document.body.append(es)
return new Promise((resolve, reject)=>{
es.onload=()=>{resolve('success '+ src)}
es.onerror=()=>{reject(new Error('failed to load '+src )) }
})
}
第二步: 鏈式調用:
load('./t1.js')
.then(str=>{
console.log(str);
f1();
return load('./t2.js') //這裏返回一個Promise
}) // 直接就能用 then
.then(str=>{
console.log(str);
f2();
return load('./t3.js') // 再返回一個Promise
})
.then(str=>{
console.log(str);
f3();
})
.catch(er=>{
console.log(er.message);
})
是不是有一種……之後的舒爽感??
Promise
極其重要的特徵是鏈式調用,每個load
函數返回一個Promise
,該Promise
在確定是異步加載成功還是失敗之後就會被返回然後提供給下一個操作使用。 一個Promise
隊列就像是使用同步代碼來實現異步操作一樣。
認識Promise
這裏就引用 MDN 上的Promise
概念:
Promise 對象是一個代理對象(代理一個值),被代理的值在Promise對象創建時可能是未知的。它允許你爲異步操作的成功和失敗分別綁定相應的處理方法。 這讓異步方法可以像同步方法那樣返回值,但並不是立即返回最終執行結果,而是一個能代表未來出現的結果的promise對象
重要說明:
Javascript
中的Promise
也參考了Promise/A+
規範。Promise
中的概念還是 ** MDN** 比較全面,因此我會根據 MDN 和 Promise/A+ 的內容綜合說明。- 嘛,其實
Promise
概念不如何重要,重要的是如何應用它。
正文開始。
Promise構造:
語法:
var p promise = new Promise(function(resolve, reject){
// executor
})
參數:
- executor : 在新建一個
Promise
實例時,必須向 Promise構造函數 傳遞一個回調函數(名曰:executor
),在新建對象時會立即執行它。
返回值: - 一個被設置了狀態的
Promise
,它可能會有value
或reason
。 亦或是該Promise
爲pending
。
附註 I:關於executor
的resolve
和reject
- 它必須有兩個回調函數:
resolve
和reject
,無論是resolve
還是reject
,這兩個都是Promise
內部提供的。 - 當首次執行
resolve
時,Promise
狀態就已經被固定爲fulfilled
,並且會有一個固定的value
- 當首次執行
reject
時,Promise
狀態就被固定爲rejected
; 並且會有一個固定的reason
- 一旦
Promise
被確定, 之後不論是再執行resolve
、reject
亦或是拋出異常
,都將被忽略。換言之,當Promise
被確定爲fulfilled
後,就無法再轉換到rejected
,反之亦然。
- 既沒有執行
resolve
也沒有reject
或拋出異常,那麼Promise
狀態爲pending
,此時它可能會被轉換到fulfilled
或rejected
。
附註 II: 關於settled
:
- 一旦
Promise
被確定爲fulfilled
或rejected
, 都可以說Promise
被settled
- 一個未被
settled
的Promise
將處於pending
狀態。
注意:
現在最好是使用Firefox瀏覽器進行實驗,因爲Chrome對console
做了優化,結果可能和理論不一致。
綜上所述,可以知道:
(一) Promise狀態一經確定(settled
),就不會再二次改變:
let pr = new Promise((res,rej)=>{
res('im fulfilled')
console.log(11111);
res('im fullfiled two')
console.log(22222);
rej(new Error('im rejected'))
throw new Error('hei, im exception')
})
console.log('33333');
console.log(pr);
瀏覽器輸出:
務必注意:
11111
、22222
、33333
是立即輸出的,因爲executor
是立即執行的。- 因爲
executor
中沒有異步操作, 所以Promise
會立即確定狀態。 - 一個
executor
是可以多次調用resolve
或reject
的,只是第一次調用纔會生效,其他全被忽略
let pr = new Promise((res,rej)=>{
rej('im rejected')
console.log(11111);
res('im fullfiled two') // 被忽略!!
console.log(22222); // 正常執行
})
pr.then(undefined,er=>{}) // 如果註釋掉,它會拋出一個異常!!!
console.log('33333');
console.log(pr);
瀏覽器輸出:
(二) Promise如果是異步確定狀態的,那麼在此之前處於pending
。
let pr = new Promise((res,rej)=>{
console.log(11111);
setTimeout(res,1000,'ha,ha,ha,ha');
console.log(22222);
setTimeout(res,500, 'en,en,en,en'); // 先執行,
})
console.log(3333);
console.log(pr); // pending
setTimeout(console.log, 1500, pr) // fulfilled
瀏覽器輸出:
根據以上推論,如果Promise
不執行resolve
時,那麼就永遠無法被settled
,即會一直處於pending
狀態。
(三) Promise是無法手動取消的
一個promise
返回後由GC
回收,並且在JS中沒有提供關於對象的銷燬方法,因此它會一直存在當當前宏任務結束。
最後
這是關於Promise
的核心部分,爲了防止知識點太分散,決定一點點的記錄筆記。要不然總是知識點一旦堆積起來就變得複雜難讀,反省了。