明天打算介紹Math.js和MathJax如何動態渲染數學方程式,其中會用到Javascript的非同步處理Promise類別,今天就在這邊介紹它。
Javascript非同步處理程式處理是程式的流程並不是一個程序完成後,才執行下一個程序,非同步的程序會等待同步的程序完成後,再開始處理。
一直覺得非同步的命名似乎和它代表的實際意義相反。
要體會或模擬非同步處理通常用setTimeout這個指令,setTimeout的第一個參數是要執行的回呼函式,第二個參數則是設定多少微秒後執行。我們用下面的例子測試非同步與同步程序執行的流程。
console.log('測試開始');
setTimeout(() => {
console.log('這是非同步程序');
}, 0);
console.log('同步程序結束');
// 執行結果
// 測試開始
// 同步程序結束
// 這是非同步程序
Promise主要ES6是為了解決回呼地獄(callback hell)的困境所建構的非同步類別,只要是Promise建構的實體,就是非同步執行,用下面的實例驗證。
console.log('非同步測試開始');
function toss() {
return new Promise(() => {
setTimeout(() => {
const result = Math.random()
if (result > 0.5) {
console.log('正面')
} else {
console.log('反面')
}
}, 100)
})
}
toss()
console.log('同步程序結束')
執行結果
非同步測試開始
同步程序結束
undefined
反面
上面的程式只是要說明Promise的非同步處理本質,通常在建構Promise實體時,會傳入一個回呼函式,這個回呼函式最多可以有二個參數,這兩個參數負責回傳資料或錯誤訊息,第一個參數通常命名為resolve,一般用來回傳工作完成得到的結果,第二個參數命名為reject,通常回傳工作失敗的資訊。Promise的實例有兩個方法的回呼函式參數可以接收resolve和reject傳回的結果或錯誤訊息作為參數。then()的回呼函式的參數為resolve回傳的結果,catch()的回呼函式的參數為resolve回傳的結果,這兩個方法都可以鏈接Promise的實例。
console.log('非同步測試開始');
function toss() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const result = Math.random()
if (result > 0.7) {
resolve('正面')
} else if (result < 0.3){
resolve('反面')
} else {
reject('重丟')
}
}, 3000)
})
}
toss().then(data => console.log('Success', data))
.catch(err => console.log('Error', err))
console.dir(toss())
Promise的實例執行時,它的狀態會是pending(懸而未決)、fullfilled(完成)和reject(拒絕)三種狀態中的一種。
雖然Promise的實例能使用then的方法來接收資料做後續的處理,語法上已經比回呼函式自然許多,但是追求更方便的想法是無止境的,如果能夠像下面的語法,不知有多大快人心。
const result = toss()
console.log(result)
可是如此寫法不就和同步語法一樣了嗎?ES6支援了類似同步處理的語法,讓我們程式撰寫更為自然。
async function getToss() {
const result = await toss()
console.log(result)
}
getToss()
也就是在處理非同步的函式前面加上關鍵字async,就可以在Promise實例前使用await關鍵字,讓語法更像同步處理,使得程式的可讀性大大提昇。
其實then也可以處理reject的回傳,但是實際上比較少人用,這邊就不提,以免愈說愈亂。
Promise類別還all和race兩個方法可以處理多個Promise實例構成陣列。all會等所有Promise實例都完成後,才算完成,回傳值是各個Promise實例的回傳值構成的陣列;而race則是第一個Promise實例完成工作,便回傳它的結果。
const f1 = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('f1 success')
}, 3000)
})
}
const f2 = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('f2 success')
}, 5000)
})
}
const f3 = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('f3 success')
}, 1000)
})
}
const promise = Promise.all([f1(), f2(), f3()])
promise.then(data => {
console.log(data)
})
console.log('同步程序結束')
上面程式,可以將「all」改成「race」便可以看出其中的差別。
對於不熟悉Javascript的人來說,Promise是比較難理解的一個主題,而它的出現也有語言發展的歷史原因,在網頁的處理也常常需要它,所以如果對它有個粗淺的了解,對網頁的學習是相當有幫助。