0%

Promise

快速導覽 :

  • Promise 例子
    • AJAX
    • 事件佇列
  • Promise 狀態
  • Promise 建立
  • Promise 回傳資料與串接
  • Promise 常用資料回傳方法
  • 透過 Promise改寫事件佇列

Promise

Promise 是 ES6所新增的建構函式,常用來處理同步與非同步的問題,也增強了程式碼的可讀性。而 JS的執行順序是由上到下,若遇到事件則為進入事件佇列( Event queue )中,就是非同步的問題,像是 :

  • AJAX行為
  • 監聽事件(click、change、…)
  • setTimeout( () => { } )

Promise 與 AJAX

我們可以用原生 fetch語法或是直接使用第三方套件的 Axios來做 AJAX行為。

這邊提供一個資料測試網址(點我),它可以取得一些 JSON格式的假資料。

axios

以下是 axios套件寫法 :

1
2
3
4
5
6
7
8
9
// 利用 axios的 get方法取得資料
// 可以接收資料後, return 資料出來傳入下一個 then使用
var url = 'https://randomuser.me/api'

axios.get(url).then(res=>{
console.log(res)
}).catch(err =>{
console.log(err)
})

fetch

利用 fetch原生語法來進行 :

1
2
3
4
5
6
7
8
9
10
11
12
var url = 'https://randomuser.me/api'

fetch(url)
.then(response => {
// 必須從 response中 return response.json()給下一個 then
// response 為被鎖定的名為的物件,ReadableStream,其中一個方法會回傳 JSON格式
// 但是網址請求已被使用中,必須使用下一個 then再度請求後回傳
return response.json();
})
.then(jsonData => {
console.log(jsonData);
})

XMLHttpRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var url = 'https://randomuser.me/api'

function get(url){
return new Promise((resolve, reject)=>{
var req = new XMLHttpRequest()
req.open('GET', url)
req.onload = function (){
if(req.status = 200){
// 成功時
resolve(req.response)
console.log(req.response)
} else {
// 失敗時
}
}
req.send()
})
}
get(url).then((res)=>{
console.log('get', res)
}).catch((err)=>{
console.log(err)
})

Promise 與事件佇列

通常我們會使用一些監聽事件等待使用者去觸發,或是用一個計時器,例如這樣 :

一般事件佇列

1
2
3
4
5
6
7
8
9
10
// setTimeout秒數就算設定為0,也會是最後出現
function fn(){
setTimeout( () => {
console.log('等待出現')
}, 0)
alert('使用者點擊')
}

var btn = document.querySelector('.btn')
btn.addEventListener('click', fn)

但如果目標是讓每經過一秒顯示一次呢?

1
2
3
4
5
6
7
8
9
// 這樣會變成 1 秒後一起出現
// 而非每秒出現一次
// 當然也可以設計成 1000 > 2000 > 3000,但這不是我們要的
setTimeout( () => {
console.log('等待出現')
}, 1000)
setTimeout( () => {
console.log('等待出現')
}, 1000)

透過 Promise來操作事件佇列

像是上述的例子,可以透過 Promise來處理問題。

以下透過 Promise來操作事件佇列 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

function activeFn(time){
return new Promise((resolve, reject)=>{
setTimeout( () => {
resolve(`${time}毫秒後自動顯示`)
}, time)
})
}

activeFn(1000).then(res=>{
// 顯示第一次
console.log(res)
return activeFn(1000)
}).then(res=>{
// 顯示第二次
console.log(res)
})


Promise 狀態

Promise會有三種狀態,分別是 :

狀態名稱 意義
Pending 待機 / 未確認,以 AJAX行為來說,就是傳入網址等待取回資料
Fulfilled 已實現狀態,以 AJAX行為來說,就是取值成功時
Rejected 已否決狀態,以 AJAX行為來說,就是取值失敗時

以上一次只會有一種狀態,並在實際使用時分別用不同的關鍵字取用。

  • 若進入 fulfilled
    • 使用 then接收上一個 return的值
    • 使用函式將值作為參數傳入 .then((參數名稱)=>{console.log(參數)})
  • 若進入 rejected
    • 使用 catch接收錯誤時會回報的值
    • 使用函式將值作為參數傳入 .catch((參數名稱)=>{console.log(參數)})

Promise 建立

  • Promise是一個內建的函式 (typeof Promise) // "function"
  • 可以使用 new運算子轉為物件,但它需要傳入一個 function才能運作
  • 根據使用者認為該
    • 情況正確使用 resoleve()回傳資料
    • 若不正確是用 reject()回傳失敗訊息
  • resolve與 reject可自定義參數名稱,不過為了避免混淆,還是習慣使用預設名稱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function promiseFn(num){
return new Promise((resolve, reject)=>{
// 非同步行為
setTimeout(()=>{
if(num){
resolve('成功!')
} else {
reject('失敗!')
}
}, 10)
})
}
promiseFn(1).then((res)=>{
console.log(res)
}).catch((res)=>{
console.log(res)
})

Promise 回傳資料與串接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function promiseFn(num){
return new Promise((resolve, reject)=>{
// 非同步行為
setTimeout(()=>{
if(num){
resolve('成功!')
} else {
reject('失敗!')
}
}, 10)
})
}
promiseFn(1).then((res)=>{
console.log(res)
return promiseFn(2)
// 這個回傳的值會給下一個 then
// 若值為假值 promiseFn(0),會直接跳至 catch
}).then((res)=>{
console.log(res)
}).catch((res)=>{
console.log(res)
return promiseFn(4)
// 這個回傳的值會給下一個 then
}).then((res)=>{
console.log(res)
})

// 直接使用 then傳入兩個函式 then(函式1, 函式2),
// 前者接收 resolve狀態,後者 reject狀態
promiseFn(1).then(
(res)=>{
console.log(res, 'success')
return promiseFn(0) // 會傳給下一個 then接收
},
(rej)=>{
console.log(rej, 'fail')
return promiseFn(1) // 會傳給下一個 then接收
}).then(
(res)=>{
console.log('成功!')
},
(rej)=>{
console.log('失敗!')
})

Promise 常用資料回傳方法

  • Promise.all
    • 等待全部執行完畢,並回傳所有結果
    • 途中只要有狀態為 reject,會直接回傳 catch結果
  • Promise.race
    • 只會回傳第一個執行完畢的函式並根據狀態回傳結果
    • 只會回傳一組資料

Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function promiseFn(num, time){
return new Promise((resolve, reject)=>{
// 非同步行為
setTimeout(()=>{
if(num){
resolve('成功!')
} else {
reject('失敗!')
}
}, time)
})
}
promiseFn(1, 2000).then((res)=>{
console.log(res)
})

// 只要途中有一個執行失敗的話,就會進入 reject狀態並回傳結果,不會再往下執行
Promise.all([
promiseFn(1, 1000),
promiseFn(0, 2000),
promiseFn(1, 3000)
]).then(res=>{
console.log(res[0], res[1], res[2])
})

Promise.race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function promiseFn(num, time){
return new Promise((resolve, reject)=>{
// 非同步行為
setTimeout(()=>{
if(num){
resolve('成功!')
} else {
reject('失敗!')
}
}, time)
})
}
promiseFn(1, 2000).then((res)=>{
console.log(res)
}).catch((res)=>{
console.log(res)
})

// 只取第一個先跑完的結果,再根據狀態回傳
Promise.race([
promiseFn(1, 1000),
promiseFn(1, 500),
promiseFn(1, 3000)
]).then(res=>{
console.log(res)
}).catch((res)=>{
console.log(res)
})

事件佇列與一般結果

若想在事件佇列執行完畢後顯示其他結果,該怎麼做?

1
2
3
4
5
6
7
8
9
10
11
12
function activeFn(time){
setTimeout( () => {
console.log(`${time}毫秒後自動顯示`)
}, time)
}
function activeFn2(){
console.log('最後顯示')
}
activeFn(3000)
activeFn2()
// "最後顯示"
// '1000毫秒後自動顯示"

利用 Promise修正結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function activeFn(time){
return new Promise((resolve, reject)=>{
setTimeout( () => {
resolve(`${time}毫秒後自動顯示`)
}, time)
})
}
function activeFn2(){
return new Promise((resolve, reject)=>{
resolve(`最後顯示`)
})
}

// 執行函式 > 接收結果,return 函式執行的結果 > 接收結果

activeFn(3000).then(res=>{
// 顯示上一個函式執行的結果
console.log(res)
// return 另一個函式執行的結果
return activeFn2()

}).then(res=>{
// 顯示上一個函式執行的結果
console.log(res)
})
// "3000毫秒後自動顯示"
// "最後顯示"

參考來源

  1. 六角學院 - 核心篇
  2. OXXO.STUDIO - JavaScript 同步延遲 ( Promise + setTimeout )