Promise
是抽象异步处理对象以及对其进行各种操作的组件。 通俗点讲,Promise
能解决由于回调嵌套带来的流程控制与可读性问题。
- 原生API函数的 Promise 化
- Promise.all 解决并行任务
- Promise.then 的链式调用
- 中断或取消 Promise 链
- 使用基于 Promise 的 async/await
- 小结
1. 原生API函数的 Promise 化
- 大部分原生的API函数并不支持
Promise
,还是基于回调来使用的,所以需要把一些方法改为返回一个Promise
对象,这个过程被称为函数的Promise
化。 - 下面一个例子将对定时器
setTimeout
Promise
化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function timer (fn, time) {
return function () {
return new Promise( (resolve, reject) => {
setTimeout(function () {
fn();
resolve();
}, time);
});
}
}
Promise.resolve()
.then(
timer(function () {
console.log('1')
}, 1000)
)
.then(() => {
console.log('2');
});
// 输出结果
// 1
// 2 Promise
化本质上都属于一种Curry(柯里)
化。Curry
化是指,将需要传递多参数的函数生成一个新的函数,如上代码先通过执行 timer得到一个新的函数,该函数会返回一个Promise
,这样就完成了Promise
化。将一些基础的函数进行Promise
化,可以
大大减少不必要的代码。- 下面的代码,将会体现这种优势:
1
2
3
4
5
6
7
8
9
10
11var promise_timer = timer(function () {
console.log('1')
}, 1000)
function promise_timer2(){
return new Promise( (resolve,reject) => {
setTimeout(function () {
console.log('1');
resolve();
}, 1000);
});
}
2. Promise.all 解决并行任务
- 当某个函数需要在
N
个回调都完成时才执行,这个时候就可以使用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
25function loadImg(src){
return new Promise( (resolve,reject) => {
var img = document.createElement("img");
img.src = src;
img.onload = function(){
resolve(img);
}
img.onerror = function(err){
reject(err);
}
});
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img);
});
}
Promise.all([
loadImg('1.png'), //加载图片
loadImg('2.png'),
loadImg('3.png'),
...
]).then(showImgs); //显示图片- 需要注意的是,
Promise.all
中传入的Promise
数组,各自resolve
之后得到的值,将合并成一个数组传入到then
中的方法,且数组中resolve
值的顺序,与Promise
数组的顺序一致。
- 需要注意的是,
3. Promise.then 的链式调用
- 在许多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
45
46
47
48
49
50
51function getUserInfo(){
console.log('getUserInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('getUserInfo end');
var userInfo = {
name: 'adam'
};
resolve(userinfo);
},1000);
});
}
function getGroupInfo(userinfo){
console.log('getGroupInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
var groupInfo = {
name: 'desc'
}
console.log('getGroupInfo end');
resolve(groupInfo,userinfo);
},1000);
});
}
function getTaskInfo(groupInfo,userinfo){
console.log('getTaskInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
var taskInfo = {
name: 'rebuild'
};
console.log('getTaskInfo end');
resolve();
},1000);
});
}
var p = Promise.resolve();
p.then(getUserInfo)
.then(getGroupInfo)
.then(getTaskInfo);
/* 输出结果
getUserInfo start
getUserInfo end
getGroupInfo start
getGroupInfo end
getTaskInfo start
getTaskInfo end
*/- 如上面代码所示,我们可以很清楚的理解到程序执行的顺序是:
- 如上面代码所示,我们可以很清楚的理解到程序执行的顺序是:
- 但是如果我们对代码进行一点小的改造,将
then
中的方法不再返回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
42function getUserInfo(){
console.log('getUserInfo start');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('getUserInfo end');
resolve();
},1000);
});
}
function getGroupInfo(){
console.log('getGroupInfo start');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('getGroupInfo end');
resolve();
},1000);
});
}
function getTaskInfo(){
console.log('getTaskInfo start');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('getTaskInfo end');
resolve();
},1000);
});
}
var p = Promise.resolve();
p.then(getUserInfo)
.then(getGroupInfo)
.then(getTaskInfo);
/* 输出结果
getUserInfo start
getGroupInfo start
getTaskInfo start
getUserInfo end
getGroupInfo end
getTaskInfo end
*/- 这是因为每次调用
then
都会返回一个新的Promise
,如果then
中的申明的方法没有返回一个Promise
,那么会默认返回一个新的
处于fulfilled
的Promise
,之后添加的then
中的方法都会立即执行,所以执行的顺序就变成这样了: - 当要在使用
链式 Promise
时,请务必在then
传入的方法中返回一个新的 Promise
。 - 另外一个需要
注意
的是,resolve 传递给下个 then 方法的值只能有一个,上面 getTaskInfo 方法中是无法获取到 userInfo 的值,所以如果有多个值需要放在一个数据集合
( Array , Object , Map , Set )中传入下个方法。
- 这是因为每次调用
4. 中断或取消 Promise 链
- Promise 标准的 API 中并没有提供相应的方法来中断或者取消 Promise 链的执行,一些库中提供了类似
Promise.break
或者Promise.fail
的方法来中断或取消 Promise 链。利用Promise.catch
的特性来中断 promise
链。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/** 用于中断的信号 */
class BreakSignal {}
Promise
.then(() => { // 1
// 开始.
})
.then(() => { // 2
if (wantToBreakHere) {
// 抛出中断信号.
throw new BreakSignal();
}
})
.then(() => { // 3
// 需要跳过的部分.
})
// 接住中断信号.
.catch(BreakSignal, () => { // 4
});- 只要在 Promise 执行过程中抛出异常,都会直接跳转到
catch
中。但是这样的做法有一个缺点,无法区分程序本身的异常,还是手动抛出的异常。所以需要手动设置一个标识标量,来区分是为了中断执行还是本身的程序异常。 - 上面的代码中,执行到
2
时,由于抛出了终端信号,所以会直接跳过3
直接执行catch
部分。
- 只要在 Promise 执行过程中抛出异常,都会直接跳转到
5. 使用基于 Promise 的 async/await
async
:1
2
3async function f() {
return 1
}- 函数前面的 async 一词意味着一个简单的事情:这个函数总是返回一个
Promise
,如果代码中有 return <非promise>语句,JavaScript会自动把返回的这个value值包装成promise的resolved值。 - 例如,上面的代码返回resolved值为1的promise,我们可以测试一下:
1
2
3
4async function f() {
return 1
}
f().then(alert) // 1 - 我们也可以显式的返回一个promise,这个将会是同样的结果:
1
2
3
4async function f() {
return Promise.resolve(1)
}
f().then(alert) // 1所以,
async
确保了函数返回一个Promise
,即使其中包含非Promise。
- 函数前面的 async 一词意味着一个简单的事情:这个函数总是返回一个
await
:1
2// 只能在 async 函数内部使用
let value = await promise;- 关键词
await
可以让JavaScript进行等待,直到一个Promise
执行并返回它的结果,JavaScript才会继续往下执行。 - 以下是一个 Promise 在 1s 之后 resolve 的例子:
1
2
3
4
5
6
7
8async function f() {
let p = new Promise((resolve, reject) => {
setTimeout(() => resolve('done!'), 1000)
})
let result = await p // 直到promise返回一个resolve值(*)
alert(result) // 'done!'
}
f() - 函数执行到(*)行会‘暂停’,当Promise处理完成后重新恢复运行, resolve的值成了最终的result,所以上面的代码会在1s后输出’done!’。
我们强调一下:
await
字面上使得JavaScript等待,直到Promise
处理完成,然后将结果继续下去。这并不会花费任何的cpu资源,因为引擎能够同时做其他工作:执行其他脚本,处理事件等等。
- 关键词
一个非常常见的栗子
: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
33function getSyncTime() {
return new Promise((resolve, reject) => {
try {
let startTime = new Date().getTime();
setTimeout(() => {
let endTime = new Date().getTime();
let data = endTime - startTime;
resolve( data );
}, 500);
} catch ( err ) {
reject( err );
}
});
}
async function getSyncData() {
let time = await getSyncTime();
let data = `endTime - startTime = ${time}`;
return data;
}
async function getData() {
let data = await getSyncData();
// 打印
console.log( data );
}
getData();
/** 500ms 后输出
* Promise {<pending>}
* endTime - startTime = 501
*/
6. 小结
- 放在一个函数前的
async
有两个作用:- 使函数总是返回一个
Promise
。 - 允许在这其中使用
await
。
- 使函数总是返回一个
Promise
前面的await
关键字能够使JavaScript等待,直到Promise
处理结束。然后:- 如果它是一个错误,异常就产生了,就像在那个地方调用了
throw error
一样。 - 否则,它会返回一个结果,我们可以将它分配给一个值。
- 如果它是一个错误,异常就产生了,就像在那个地方调用了
- 有了
async/await
,我们很少需要写Promise.then/catch
,但是我们仍然不应该忘记它们是基于Promise
的,因为有些时候(例如在最外面的范围内)我们不得不使用这些方法。
参考文档 Promise