一、非同步程式設計
- 非同步程式設計的概念:非同步程式設計(Asynchronous, async)是相對於同步程式設計(Synchronous, sync)的;我們在學習傳統的單執行緒程式設計中,執行的程式都是按照同步執行的,(注意 :同步不意味著所有步驟同時執行,而是指步驟在一個控制流序列中按順序執行),而非同步的概念則是不保證同步的概念,也就是說,一個非同步過程的執行將不再與原有的序列有順序關係。
簡單的解釋就是:同步是按照你程式碼的順序執行的,而非同步則不按照你程式碼順序執行,非同步的執行的效率會更高。
什麼時候使用非同步程式設計:在前端程式設計中(甚至後端有時也是這樣),我們在處理一些簡短、快速的操作時,例如計算 1 + 1 的結果,往往在主執行緒中就可以完成。主執行緒作為一個執行緒,不能夠同時接受多方面的請求。所以,當一個事件沒有結束時,介面將無法處理其他請求。
現在有一個按鈕,如果我們設定它的 onclick 事件為一個死迴圈,那麼當這個按鈕按下,整個網頁將失去響應。
為了避免這種情況的發生,我們常常用子執行緒來完成一些可能消耗時間足夠長以至於被使用者察覺的事情,比如讀取一個大檔案或者發出一個網路請求。因為子執行緒獨立於主執行緒,所以即使出現阻塞也不會影響主執行緒的執行。但是子執行緒有一個侷限:一旦發射了以後就會與主執行緒失去同步,我們無法確定它的結束,如果結束之後需要處理一些事情,比如處理來自伺服器的資訊,我們是無法將它合併到主執行緒中去的。
為了解決這個問題,JavaScript 中的非同步操作函式往往透過回撥函式來實現非同步任務的結果處理。
二、 Promise
構造Promise:
現在我們構造一個Promise的類
function Promise (function(resolve, reject){ //要做的事 })
當我們遇到一些非同步任務,如果是一次還好,那如果是多次呢,例如:現在我們需要分三次輸出字串,第一次間隔 1 秒,第二次間隔 4 秒,第三次間隔 3 秒,在此之前我們是這樣實現的:
setTimeout(function (){ console.log('Frist') setTimeout(function (){ console.log('Second') setTimeout(function (){ console.log('Thirt') },3000) }, 4000) }, 1000)
仔細看著程式碼是不是很複雜,這段程式完成了我們的目的可見維護起來是一件非常繁瑣的事。
那麼我們現在用Promise來實現:
new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Frist') resolve() }, 1000) }).then(function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Second') resolve() }, 4000) }).then(function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('Thirt') resolve() }, 3000) })
其實這段程式碼仍然很長,而且不好理解,這裡我們並不需要看懂
Promis的使用:
事例我們仍然使用上面的計時器,Promise只是一個函式,他的引數也是一個函式,
new Promise(function (resolve, reject)
而Promise函式中的引數函式中的引數同樣是函式即:resolve()
和reject()
當Promise被構造時,起始函式就是非同步執行,呼叫函式resolve(res)
表示一切正常執行,並會將res
給入下一個呼叫中;呼叫函式reject()
表示出現異常。new Promise(function (resolve, reject) { var a = 0 var b = 1 if (b === 0) { reject("Divide zero") }else { resolve(a / b) } }).then(function (value) { console.log("a / b = " + value); }).catch(function (err) { console.log(err) }).finally(function () { console.log("End") })
程式輸出結果:
a / b = 0 End
這裡:
function().then(function(Res)) //會將上一次呼叫的resolve(res)中的res給到Res
function().catch(function(Res)) //會將上一次呼叫的reject(res)中的res給到Res,會將我們提前處理的錯誤返回給Res,這樣一來程式不會掛掉
但是請注意以下兩點:
resolve 和 reject 的作用域只有起始函式,不包括 then 以及其他序列;
resolve 和 reject 並不能夠使起始函式停止執行,別忘了 return。
這裡做完整的補充:Promise 類有 .then() .catch() 和 .finally() 三個方法,這三個方法的引數都是一個函式,.then() 可以將引數中的函式新增到當前 Promise 的正常執行序列,.catch() 則是設定 Promise 的異常處理序列,.finally() 是在 Promise 執行的最後一定會執行的序列。 .then() 傳入的函式會按順序依次執行,有任何異常都會直接跳到 catch 序列:
new Promise(function (resolve, reject) {
console.log(1111)
resolve(2222)
}).then(function (value) {
console.log(value)
return 3333
}).then(function (value) {
console.log(value)
throw "An error"
}).catch(function (err) {
console.log(err)
});
輸出:
1111
2222
3333
An error
三、Promise函式
- Promise計時器的實現
function print(delay, message):Promise<string>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log('message')
resolve()
}, delay)
})
}
呼叫:
print(1000, 'Frist').then(()=>{
return print(4000, 'second').then(res=>{
return print(3000, 'Thirt').then(res=>{
consoloe.log('End')
})
})
})
輸出:
"message:", "frist"
"message:", "second"
"message:", "thirt"
"end"
加法 + 乘法的回撥
//返回Promise<number>型別 function add(a: number, b: number): Promise<number> { console.log('start promise') //promise的引數是一個帶有兩個函式為引數的函式:function: (resolve, reject) return new Promise((resolve, reject) =>{ if(b % 17 === 0){ //這裡找17的模, return reject(`bad number: ${b}`) } //setTimeout(function:, timeout?)系統函式, setTimeout(()=>{ //resolve具體是什麼,我們不需要了解 resolve(a + b) }, 2000) }) }
呼叫:
add(2, 3).then(res =>{ console.log('2 + 3', res) resolve(res) return add(res, 4).then(res =>){ console.log('2 + 3 + 4', res) } }
2 + 3, 5 2 + 3 + 4, 9
乘法:
function mul(a: number, b: number): Promise<number> { return new Promise((resolve, reject) =>{ setTimeout(()=>{ resolve(a * b) }, 3000) }) }
呼叫:
//(2 + 3) * 4 + 4 add(2, 3).then(res =>{ console.log(res) return mul(res, 4).then(res =>{ console.log(res) return add(res, 5).then(res =>{ console.log('(2 + 3) * 4 + 4', res) }).catch(err =>{ console.log('cauht err') }) }) })
輸出:
5 20 (2 + 3) * 4 + 4, 24
四、Promise處理多工
我們經常在網路中,會同時處理多個任務,這也是Promise的真正意義,現在我們來看看簡單的處理多工:Promise.all()
方法會返回相應操作的結果,並且以陣列的形式返回
//處理多個請求
//(2 + 3) * (4 + 4) * (20 + 21)
//Promise.all()返回一個對應引數數量的陣列
Promise.all([add(2, 3), add(4, 4)]).then(res =>{
res[0], res[1] //const [a, b, c] = res
console.log(res[0], res[1])
return mul(res[0], res[1], ).then(res =>{
console.log('(2 + 3) * (4 + 4)=',res)
})
})
輸出:
(2 + 3) * (4 + 4)=, 40
這裡另外介紹一個方法:Promise.race()
傳入多個引數,只返回一個結果並且是執行最快的結果
Promise.race([add(2, 3), add(4, 4)]).then(res =>{
console.log(res)
})
輸出:
5
五、async/awai語法糖
在TS中我們可以使用async/awai
來簡化我們在上面的程式碼,讓我們的程式碼更為直觀。
這是我們之前的做法:
//(2 + 3) * 4 + 4
add(2, 3).then(res =>{
console.log(res)
return mul(res, 4).then(res =>{
console.log(res)
return add(res, 5).then(res =>{
console.log('(2 + 3) * 4 + 4', res)
}).catch(err =>{
console.log('cauht err')
})
})
})
我們使用async/awai
後是這樣的
//(2 + 3) * (3 + 4)
async function calc(){
try{
const [a, b] = await Promise.all([add(2, 3), add(3, 4)])
console.log('2 + 3:', a)
console.log('3 + 4:', b)
return await mul(a, b)
}catch(err){
console.log('catch err', err)
return undefined
}
}
這裡需要注意的是:
async function func_name(){
await ...
}
async
需要在函式定義的欄位前使用await
必須在函式中使用
本作品採用《CC 協議》,轉載必須註明作者和本文連結