JavaScript - Promise




過去以 Callback 撰寫非同步會容易在呼叫多層之後產生「Callback Hell」難以維護的問題,而 ES6 提供 Promise 來更方便的執行非同步。關於 JavaScript 執行方式、非同步及 Callback 原理解釋請參考前文:

在還沒有 Promise 的時候,我們為了控制多個函式間執行的順序,就會透過 Callback 的形式將 function 當作參數傳進去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 為了確保先執行 first 再執行 second
// 我們在 first 加上 callback 參數
var first = function(callback){
setTimeout(function(){
console.log("This is first.");

callback();
}, 1000);
};


var second=function(){
console.log("This is second.");

};
// 將 second 作為參數帶入 first()
first(second);
//"This is first."
//"This is second."

無論 first() 在執行的時候要等多久, second() 都會等到 first() 執行完才會執行。也可以寫成下面這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function first(callback) {
setTimeout(() => {
    console.log("This is first.");
callback()
}, 1000)
}

function second(callback) {
callback(() => {
console.log("This is second.");
})
}
second(first)

//"This is first."
//"This is second."

如果再增加 Callback 的數量:

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
function first(callback2) {
setTimeout(() => {
    console.log("This is first.");
callback2()
}, 1000)
}

function second(callback) {
setTimeout(() => {
    console.log("This is second.");
callback()
}, 1000)

}

function third() {
console.log("This is third.");

}

function main(callback) {
first(() => {
second(() => {
callback()
})
})
}
main(third)

//"This is first."
//"This is second."
//"This is second."

當 Callback 太多層就會變成非常複雜的巢狀結構:

1
2
3
4
5
6
7
8
9
first(() => {
second(() => {
third(() => {
fourth(() => {
// ......
})
})
})
})

Promise

Promise 提供的新語法讓非同步更加直觀,讓我們看一下它的建構函式:

1
new Promise( function(resolve, reject) { ... } )

下面是箭頭函式的寫法:

1
new Promise( (resolve, reject) => { ... } )

Promise 的狀態與流程

Promise 有三種 state(狀態)

  • pending:等待(還在執行中且結束狀態未知)

  • resolved/fullfilled:完成/成功

  • rejected:拒絕/失敗

Promise 運作流程圖:

來源:MDN

Promise 物件中包含兩個參數: resolverejectthen 會接收 resolve 的 value, catch 則接收 reject 的 value。

讓我們看一個基本的 Promise 範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p = new Promise(function(resolve, reject) {
setTimeout(function(){
let value = 1
console.log(value);
resolve(value);
}, 1000);
});

//Promise fullfilled handler
p.then((value) => {
// 在 p 被 resolve 時執行
console.log('success:' + value)
})
//Promise rejected handler
p.catch((error) => {
// 在 p 被 reject 時執行
console.log('error:', error)
})

Pending

Promise 沒有 resolve 或是 reject 的時候,程式將會一直處在 pending 狀態:

1
2
3
4
5
6
7
var p = new Promise(function(resolve, reject) {});
p.then((value) => {
console.log('success');
})
p.catch((error) => {
console.log('error:', error);
})

例如:送出一個請求一直沒有收到回應時, Promise 就會一直處於 pending 狀態。

Promise Chain

我們可以在 Promise 使用 .then 來進行串接,透過在 .then 裡面 return 一個值,讓這個回傳值以 Promise 物件的形式傳到下一個 .then ,形成 Promise Chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const p = new Promise(function(resolve, reject) {
setTimeout(function(){
let value = 1;
resolve(value);
}, 1000);
});

p.then((value) => {
// 得到 resolve 內的值
console.log(value); // 1
return value+1;
}).then((value) => {
// 得到上一個 .then return 的值
console.log(value); // 2
return value+1;
}).then((value) => {
// 得到上一個 .then return 的值
console.log(value); // 3
}).catch((error) => {
console.log('error:', error)
})

catch 如果是串在中間,在 new Promise 的時候是 reject ,會導致前面 .then 都不會被執行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p = new Promise(function(resolve, reject) {
setTimeout(function(){
let value = 1;
reject(value);
}, 1000);
});

p.then((value) => {
console.log('Start');
return value+1;
}).catch((error) => {
console.log('error:', error)
}).then((value) => {
console.log('End');
})

//error: 1
//End

Promise 使用範例

我們可以在 .thenreturn 一個 new 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
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let value = 1;
resolve(value)
}, 1000)
})

p.then((value) => {
console.log("This is the first value :" + value);
return value + 1
})

.then((value) => {
console.log("This is the second value :" + value);
// return 一個 new Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value + 2)
}, 1000)
})
})

// 等 Promise resolve 後才會執行。
.then((value) => {
console.log("This is the third value :" + value);
})
.catch((error) => {
console.log('error:', error)
})

/*Output:
This is the first value: 1
This is the second value: 2
This is the third value: 4
/*

範例二:

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 p(value) {
return new Promise((resolve, reject) => {
setTimeout(function() {
value++
resolve(value)
}, 1000)
})
}

p(1)
.then((value) => {
console.log("This is the first value :" + value);
return p(value+1)
})
.then((value) => {
console.log("This is the second value :" + value);
return p(value+1)
})
.then((value) => {
console.log("This is the third value :" + value);
})

/*Output:
This is the first value: 2
This is the second value: 4
This is the third value: 6
/*

範例三:

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 p(value) {
console.log("This value is :" + value);
return new Promise((resolve, reject) => {
setTimeout(function() {
value++
resolve(value)
}, 1000)
})
}

p(1)
.then(p)
.then((value) => {
console.log(value)
return value+2
})
.then(p)
.catch((error) => {
console.log('error:', error)
})

/*Output:
This value is :1
This value is :2
3
This value is :5
/*

範例四:

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
const p = new Promise(function(resolve, reject) {
setTimeout(function(){
let value = 1;
resolve(value);
}, 1000);
});

function funA (value) {
console.log("The value of function A is :" + value);
const data = funC(value)
return data + 1
}

function funB (value) {
console.log("The value of function B is :" + value);
}

function funC (value) {
return value + 2
}

p.then(funA)
.then(funB)

/*Output:
The value of function A is :1
The value of function B is :4
/*

Promise 錯誤處理

通常我們會將 .catch 放在最後面做錯誤處裡,當其中一個 .then 發生錯誤時,就會跳到 .catch ,而 .catch 之後的 .then 皆不會執行。

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
function p(value) {
return new Promise((resolve, reject) => {
if (value > 2){
reject('Unexpected condition')
}
setTimeout(function() {
value++
resolve(value)
}, 1000)
})
}

p(1)
.then((value => {
console.log("This is the first value :" + value);
return p(value)
}))
.then((value => {
console.log("This is the second value :" + value);
return p(value)
}))
.then((value => {
console.log("This is the third value :" + value);
return p(value)
}))
.catch((error) => {
console.log(error)
})

/*Output:
This is the first value :2
This is the second value :3
Unexpected condition
/*

Promise.all

Promise.all 可以傳入一個以上的 Promise 並同時執行,等到所有的 Promise 都回應狀態後,才會進入 .then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function p(value) {
return new Promise((resolve, reject) => {
setTimeout(function() {
if (value < 1000 ){
        reject('Unexpected condition')
}
resolve(value)
}, value)
})
}

Promise.all([p(1000), p(2000), p(3000)]).then((value)=> {
console.log('This value is :', value);
}).catch( err => {
console.log(err)
});

/*
[This value is : 1000 , This value is : 2000 , This value is : 3000]
*/

如果其中一個 Promise 中有出現 reject ,則直接進入 .catch 階段,而不會收到其他 Promise resolve 的回傳值。

Promise.race

Promise.race 一樣可以傳入多個 Promise 並同時執行,但只回傳 Promise 中最快完成的,並接著執行 .then ,而其他的 Promise 皆不會執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function p(value) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(value)
}, value)
})
}

Promise.race([p(1000), p(2000), p(3000)]).then((value)=> {
console.log('This value is :', value);
}).catch( err => {
console.log(err)
});

/*
This value is : 1000
*/

參考文獻

  1. callback, promise, async/await 使用方式教學以及介紹 Part I - Jack Yu | 傑克
  2. [JS] Promise 的使用 | PJCHENder 私房菜
  3. Day23 Promise 詳解(1/2)
  4. Day24 Promise 詳解(2/2)
  5. JavaScript - Promise (2)
  6. JS 原力覺醒 Day14 - 一生懸命的約定:Promise