(一)Promise的错误处理
①通常的异常捕获
错误处理最自然的形式就是同步的 try…catch 结构。遗憾的是,它只能是同步的,无法用于异步代码模式(参考下列代码)。
function foo() {
setTimeout( function(){baz.bar();}, 100 );
}
try {
foo();
// 100毫秒后从 `baz.bar()` 抛出全局错误
}catch (err) {// 永远不会到达这里}
②error-first 回调风格的错误处理
下列方式只能捕获第一重异步的错误,若有多重则要多重捕获(强烈建议不要使用)。
function foo(cb) {
setTimeout(
function(){
try {
var x = baz.bar();//如果 baz.bar() 本身有自己的异步完成函数,其中的任何异步错误都将无法捕捉到。
cb( null, x ); // 成功!
}catch (err) {
cb( err );
}
}, 100 );
}
foo( function(err,val){
if (err) {
console.error( err );
} else {
console.log( val );
}
} );
严格说来,这一类错误处理是支持异步的,但完全无法很好地组合。多级 error-first 回调交织在一起,再加上这些无所不在的 if检查语句,都不可避免地导致了回调地狱(多重嵌套回调)的风险。
③Promise的错误处理
- 通过链式调用:
var p = Promise.resolve( 42 );
p.then(
function fulfilled(msg){
// 数字没有string函数,所以会抛出错误
console.log( msg.toLowerCase() );
},
function rejected(err){
// 永远不会到达这里
}
)
.then(
function(){},
function rejected(err){
//会在此处运行
}
);
- 通过catch()
var p = Promise.resolve( 42 );
p.then(
function fulfilled(msg){
// 数字没有string函数,所以会抛出错误
console.log( msg.toLowerCase() );
}
)
.then(
function(){
...
}
)
.catch( handleErrors );
因为我们没有为这个 Promise 链中的任何一个 then(…) 传入拒绝处理函数,所以默认的处理函数被替换掉了,而这仅仅是把错误传递给了链中的下一个 promise,所以对于链中任何位置出现的任何错误,这个处理函数(handleErrors )都会得到通知。因此,进入 p 的错误以及 p 之后进入其决议(就像 msg.toLowerCase())的错误都会传递到最后的 handleErrors(…)。
如果 handleErrors(…) 本身内部也有错误怎么办呢?谁来捕捉它?还有一个没人处理的promise:catch(…) 返回的那一个。我们没有捕获这个 promise 的结果,也没有为其注册拒绝处理函数。
(二)Promise并行构建模式
(2.1)Promise.all([ … ])
(1)概念
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
上述代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。如果数组是空的,主 Promise 就会立即完成。)
p的状态由p1、p2、p3决定,分成两种情况:
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
(2)应用
实现:同时发送两个 Ajax 请求,等它们不管以什么顺序全部完成之后,再发送第三个 Ajax 请求。
// request(..)是一个Promise-aware Ajax工具
var p1 = request( "http://some.url.1/" );
var p2 = request( "http://some.url.2/" );
Promise.all( [p1,p2] )
.then( function(msgs){
// 这里,p1和p2并行执行,完成并把它们的消息传入
return request(
"http://some.url.3/?v=" + msgs.join(",")
);
} )
.then( function(msg){
console.log( msg );
} );
(2.2)Promise.race([ … ])
(1)概念
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1, p2, p3]);
上述代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数(只响应“第一个跨过终点线Promise”,而抛弃其他 Promise)。
Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
与Promise.all的区别: 如果传入了一个空数组,主race([…]) Promise 永远不会决议,而不是立即决议。
(2)应用
超时竞赛:
// 为foo()设定超时
Promise.race( [
foo(), // 启动foo()
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 3000);
}) // 给它3秒钟
] )
.then(
function(){
// foo(..)按时完成!
},
function(err){
// 要么foo()被拒绝,要么只是没能够按时完成,
// 因此要查看err了解具体原因
}
);
(2.3)all和race的变体
first([ … ]): 只要第一个 Promise 完成,它就会忽略后续的任何拒绝和完成。
// polyfill
if (!Promise.first) {
Promise.first = function(prs) {
return new Promise( function(resolve,reject){
// 在所有promise上循环
prs.forEach( function(pr){
// 把值规整化
Promise.resolve( pr )
// 不管哪个最先完成,就决议主promise
.then( resolve );
} );
} );
};
}
(2.4)异步的 map(…) 工具
它接收一个数组的值(可以是Promise 或其他任何值),外加要在每个值上运行一个函数(任务)作为参数。map(…) 本身返回一个 promise,其完成值是一个数组,该数组(保持映射顺序)保存任务执行之后的异步完成值。
//Pollyfill函数
if (!Promise.map) {
Promise.map = function(vals,cb) {
// 一个等待所有map的promise的新promise
return Promise.all(
// 注:一般数组map(..)把值数组转换为 promise数组
vals.map( function(val){
// 用val异步map之后决议的新promise替换val
return new Promise( function(resolve){
cb( val, resolve );
} );
} )
);
};
}
调用:
var p1 = Promise.resolve( 21 );
var p2 = Promise.resolve( 42 );
var p3 = Promise.reject( "Oops" );
// 把列表中的值加倍,即使是在Promise中
Promise.map( [p1,p2,p3], function(pr,done){
// 保证这一条本身是一个Promise
Promise.resolve( pr )
.then(
// 提取值作为v
function(v){
// map完成的v到新值
done( v * 2 );
},
// 或者map到promise拒绝消息
done
);
} )
.then( function(vals){
console.log( vals ); // [42,84,"Oops"]
} );