promise 經常與 Ajax 共同談論,但這篇文章會以 promise 為主;promise 是一個語法,專門用來處理與優化非同步行為,我們知道 JavaScript 屬於同步程式語言,因此它一次只會做一件事,遇到非同步事件的時候,就會等到所有的原始碼運行完,才會執行非同步的事件。
這邊強調的是執行的順序,只要是非同步一定都會放在最後才執行,即使 setTimeout()
的時間設定是 0,也同樣放到最後。
function getData() {
setTimeout(() => {
console.log('... 已取得遠端資料');
}, 0);
}
const component = {
init() {
console.log(1);
getData(); // 非同步
console.log(2);
}
}
component.init();
結果:
1
2
... 已取得遠端資料
promise 是一個建構式函式,函式也屬於物件,我們可以附加其他屬性方法,promise 可以直接使用 all
、race
、resolve
、reject
等方法,下面這幾種方法皆可以直接 console 查看:
promise 建構函式 new
出的新物件,也可以在 prototype
內使用其中的原型方法,在這裡面也包含了 then
、catch
、finally
,如果要呼叫他們,則必須在新產生的物件下才能呼叫。
透過 new Promise()
的方法建立 a
物件,a
就能使用 promise 的原型方法,範例如下:
const a = new Promise();
a.then(); // promise 回傳正確
a.catch(); // promise 回傳失敗
a.finally(); // 非同步執行完畢(無論是否正確完成,非同步都會在最後被執行完畢)
在 promise 事件裡,我們必須給予參數讓它回傳結果,通常會給予 resolve
、reject
這兩個參數,他們分別代表回傳成功及失敗,並且僅能回傳其中一個。另外,這兩個參數也可以自己定義名稱,但大部分的開發者會習慣用本來的名字。
new Promise(function(resolve, reject){
resolve(); // 正確完成的回傳
reject(); // 失敗的回傳
});
promise 的執行過程中也包括下面這幾種狀態:
resolve
的結果,resolve
會回傳成功。reject
的結果,reject
會回傳錯誤。以下範例可以大致說明 promise 的運行過程:
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
// setTimeout 在這裡就是一個非同步的狀態
setTimeout(() => {
if (status) {
resolve('promiseSetTimeout 成功')
} else {
reject('promiseSetTimeout 失敗')
}
}, 0);
})
}
pending 之後會有兩個狀態 Fulfilled 和 Rejected,Fulfilled ( 成功 ) 會使用 resolve
回傳結果,並用 then
來做接收,Rejected ( 失敗 ) 會使用 reject
回傳結果,並用 then
或 catch
來做接收,但如果前面有 then
,則會跳過,直接用 catch
做接收。
另外,我們要如何確定 promise 是否完成?這時候可以依據 resolve
及 reject
是否有被調用,如果沒有被調用, promise 的結果會停留在 pending
。
function promise() {
return new Promise((resolve, reject) => {});
}
console.dir(promise()); // [[PromiseState]]: "pending"
執行上面程式碼,我們可以看到在 promise 函式中有下面兩種屬性:
"pending"
目前的進度狀態。undefined
為 resolve
或 reject
回傳的值。promise
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
函式陳述式建立以後,透過 return new Promise
回傳並建立一個 promise 物件,在內部執行 promise 函式並加入參數 resolve
和 reject
,這個階段就是常見的 promise 結構,接著等待 resolve
、reject
回傳結果就可以完成整個程序。
這個範例會隨機調用 resolve 和 reject:
function promise() {
return new Promise((resolve, reject) => {
// 隨機取得 0 or 1
const num = Math.random() > 0.5 ? 1 : 0;
// 1 則執行 resolve,否則執行 reject
if (num) {
resolve('成功');
}
reject('失敗')
});
}
第二個範例也可以看到函式最後的結果是回傳 resolve,並且能夠回傳 resolved 的狀態和值。
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status) {
resolve('promiseSetTimeout 成功')
} else {
reject('promiseSetTimeout 失敗')
}
}, 0);
})
}
promiseSetTimeout(true)
.then(function(res){
console.log(res); // "promiseSetTimeout 成功" 回傳成功
})
結果:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
promise 在 then
、catch
都可以使用鏈接的方式不斷的進行任務,這個範例的 promise 傳入 0
會調用 reject
,其他數值則是 resolve
。
function promise(num) {
return new Promise((resolve, reject) => {
num ? resolve(`${num}, 成功`) : reject('失敗');
});
}
如果我們希望 promise 可以接著進行下一個任務,可使用 return
進入下一個 then
,這邊的 return
也有一些特點:
return
會遵循 then
及 catch
的運作。then
則可以取得結果。promise(1)
.then(success => {
console.log(success);
return promise(2);
})
.then(success => {
console.log(success);
return promise(0); // 這個階段會進入 catch
})
.then(success => { // 由於上一個階段結果是 reject,所以此段不執行
console.log(success);
return promise(3);
})
.catch(fail => {
console.log(fail);
})
上面有提到幾個 promise 的方法,這邊回頭來討論一下,在 promise物件下,展開可以看到這些方法。
以陣列的形式傳入數個 promise 函式,並且以同樣的順序回傳,且為陣列結果。
Promise.all([promise(1), promise(2), promise(3, 3000)])
.then(res => {
console.log(res);
});
以陣列的形式傳入數個 promise 函式,但在全部執行完畢以後只會回傳單一個結果,並且回傳的是第一個執行完畢的陣列,以下列範例來說,回傳的是 promise(1)
。
Promise.race([promise(1), promise(2), promise(3, 3000)]).then(res => {
console.log(res);
});
這兩個方法其實就是回傳 promise 運行完畢的狀態;resolve
回傳操作成功,reject
回傳操作失敗,並且通常只會回傳其中一個結果。
var result = Promise.resolve('result');
result.then(res => {
console.log('resolved', res); // 成功部分可以正確接收結果
}, res => {
console.log('rejected', res); // 失敗部分不會取得結果
});
此篇文章感謝卡斯伯老師詳細的文章講解,為了學習和記憶我也盡量用自己的話整理出來,如果有理解錯誤的地方也請看過的大神們不吝嗇告知,或也可以看看卡斯伯老師詳細的 promise 全介紹。
參考資料: