同步 Sync 與非同步 Async

同步與非同步的差異

在學習同步與非同步的概念時很容易混淆,「同步」光看字面上的意思,可能會誤解成「所有動作同時進行」,而其實正好相反。

就以去夜市點餐來說明,如果今天想買的東西有:雞排、QQ球、珍奶。

  • 同步 (sync):先去買雞排 -> 再去買QQ球 -> 然後再買珍奶。
    像這樣「先完成 A 才能做 B、C、D …」的運作方式我們就會把它稱作「同步」。

  • 非同步 (async):再找兩個朋友幫忙,每人去一個攤位買一樣東西,就可以同時買好雞排、QQ球、珍奶。

所以同步的概念其實比較像是「一步一步來處理」的意思。
非同步則是,我不用等待 A 做完才做 B、C,而是這三個事情可以同時發送出去。

Promise

Promise 是 ES6 新增的建構函式,用來優化非同步的語法,增加可讀性。
Promise 字面上翻譯就是「約定、承諾」,可以想像成 A 和 B 約定要做某件事情,接著回報處理結果,而這個結果只能是「完成」或「拒絕」。

1
2
3
4
5
function asyncFunction (value) {
return new Promise((resolve, rejecte) => {
value ? resolve('處理完成的結果') : reject('被拒絕的原因')
})
}

為什麼需要 Promise?

JavaScript 是單執行緒(同步)的程式語言,一次只能處理一件事情,所以遇到非同步的事件時,就會先把程式碼放到「事件佇列」,等到所有事件處理完後才會執行非同步事件。

1
2
3
4
5
6
7
console.log('開始') // 執行順序 1

setTimeout(() => {
console.log('非同步事件') // 執行順序 3
}, 0)

console.log('程式碼結束') // 執行順序 2

狀態

Promise 在處理非同步的事件的過程中,包含不同的進度狀態

  • Pending:尚未得到結果
  • Resolved:事件已經執行完畢且成功操作,回傳 resolve 的結果
  • Rejected:事件已經執行完畢但操作失敗,回傳 reject 的結果
    Image

接收回傳

Promise 可以用 thencatch 來接收並回傳結果:
then 可以同時接收成功、失敗結果,而 catch 只接收失敗結果

  • 使用 catch 接收失敗:
    在任何階段遇到 reject 時,都會直接跳到 catch,之後的 then 都不會執行
    雖然 catch 依然可以使用 return 繼續串接,但很少這樣使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
promise(1)
.then(success => {
console.log(success) // resolve 接收成功 // '1'
return promise(0) // return promise(0) -> reject 跳到 catch
})

.then(success => { // 因為上面出現 reject 所以跳過
console.log(success)
return promise(3)
})

.catch( fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})
  • 使用 then 接收失敗:
    then 中的兩個函式必定執行其中一個,可以用此方式確保所有的鏈接都能夠被執行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
promise(0)
.then(success => {
console.log(success)
return promise(1)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
return promise(2) // return promise(2)
})

.then(success => {
console.log(success) // resolve 接收成功 // '2'
return promise(0) // return promise(0)
}, fail => {
console.log(fail)
return promise(4)
})

.then(success => {
console.log(success)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})

完成

  • 最後可以使用 finally 來確認工作結束
  • finally 不帶有任何參數,適合用來確認 Ajax 已經讀取完成
1
2
3
4
5
6
promise(1)
.then(success => {
console.log(success)
}).finally(() => {
console.log('done')
})

參考文章