Javascript中的异步与性能(三<3>) -- Promise的错误处理与Promise并行构建模式

(一)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"] 
} );
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Funnee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值