JavaScript ES6 原生Promise的简单介绍与坑

使用javascript做开发,一定免不了异步调用,尤其是Nodejs,所有api几乎都是异步的,因此很容易产生如下代码:

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
function someAction(arg, callback) {
//第1层调用
handler.func1(arg, function(err1, res1) {
if (err1) {
callback(err1, null);
}
else {
//第2层调用
handler.func2(res1, function(err2, res2) {
if (err2) {
callback(err2, null);
}
else {
//第3层调用
handler.func3(res2, function(err3, res3) {
if (err3) {
callback(err3, null);
}
else {
callback(null, res3);
}
})
}
})
}
})
}

function main() {
//调用函数
someAction('arg', function(err, res3) {
if (err) {
//操作失败
}
else {
//操作成功
}
})
}

//以上所调用的func1,func2,func3为以下类似代码
//用setTimeout来模拟耗时操作
handler.funcx = function(argx, callback) {
setTimeout(function() {
callback(null, 'resx');
//or callback(new Error('err'), null);
}, 1000);
}

此处只有3层调用,还勉强能看,但是如果有4层,5层或者更多,中间再加上一个判断,代码结构简直就没法看了,这就是所谓的 callback hell(回调地狱)

幸好,自从javascript进入的 ES6(ECMA 2015),官方提供了一个解决方案来应对这个问题,就是 Promise

什么是Promise

一个 Promise 对象可以理解为一次将要执行的操作(常常被用于异步操作),使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观。

上面那段代码,改成使用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
function someAction(arg) {
return handler.func1(arg).then(res1 => {
return handler.func2(res1);
}).then(res2 => {
return handler.func3(res2);
});
}

function main() {
someAction('arg').then(res3 => {
//调用成功,此处返回func3的结果res3
}).catch(err => {
//调用失败
})
}

//只需要对handler.funcx函数做一点改动
handler.funcx = function(argx) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve('resx'); //使用resolve标记成功状态,参数为需要返回的值
//or reject(new Error('err')); //使用reject标记失败状态,参数为err
}, 1000);
})
}

从代码层面看是不是简单不少~

这里对初学者来说要解释一下,在ES6中function可以使用lambda表达式简写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ES5
var func = function(arg1, arg2) {
//body
return arg1 + arg2;
}

//ES6
var func = (arg1, arg2) => {
//body
return arg1 + arg2;
}
//如果只有一个参数还可以简写
var func = arg1 => {
//body
return arg1 + '1';
}
//如果body中只有一句return语句,还能简写
var func = arg1 => arg1 + '1';

下面继续回到Promise中。

resolve & reject

new Promise((resolve, reject) => {...}),其中出现了resolvereject

  • resolve方法可以使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作;
  • reject方法则是将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作;
  • 注意: resolvereject在同个Promise对象中只会有一个发生。

then & catch

  • Promise内发生了resolve,便会调用then中的方法;
  • Promise内发生了reject,便会调用catch中的方法。

thencatch可以组合成链式结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func1(arg).then(val1 => {
return val2;
}).then(val2 => {
//这里的val2是上个then返回的val2
return func2(arg2); //这里返回了一个Promise对象
}).then(val3 => {
//这里的val3是func2返回Promise中resolve的参数值
return val3;
}).catch(err1 => {
//以上任意一个Promise发生reject,都会往下传到catch中而忽略catch之前的其他then
//catch中可以继续返回Promise
return Promise.resolve('arg3'); //这种是直接返回一个已经是resolve状态的Promise对象
}).then(arg3 => {
//还可以继续...
})

thencatch中可以返回任何值:

  • thencatch中返回非Promise对象的值,值会传递到下一个then中;
  • 返回Promise对象,会在Promise产生结果后传递到下一个thencatch中。

Promise.all & Promise.race

用法:

1
2
3
4
5
6
7
8
9
10
Promise.all([
func1(), //这两个方法返回值都是Promise对象
func2()
]).then(results => {
//直到func1,func2返回的Promise全部都resolve后,调用该then
//results[0] func1返回Promise中resolve参数值
//results[1] func2返回Promise中resolve参数值
}).catch(err => {
//返回首先调用reject的那个Promise的reject参数值
})
1
2
3
4
5
6
7
8
9
Promise.race([
func1(), //这两个方法返回值都是Promise对象
func2()
]).then(result => {
//直到func1,func2返回的Promise有任意一个resolve后,调用该then
//注意:一旦这个then被调用了,之后也不会有任何后续catch被调用
}).catch(err => {
//直到func1,func2返回的Promise有任意一个reject后,且之前没有任何then被调用过,调用该catch
})

遇到过的坑

  • 在每个then中,只要有后续的then,都必须有返回,这点最容易产生疏忽;
  • Promise.all和Promise.race中只能接受Promise数组[p1, p2],不接受对象{name1: p1, name2: p2}
  • 最后要有一个catch,否则错误会抛出来。
EOF