Javascript中的异步与性能(三<1>) -- Promise概述

(一)什么是 Promise?

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

(二)Promise中有3个状态

pending: 进行状态(未完成状态).
fulfilled: 成功的操作.
rejected: 失败的操作.

一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

(三)resolve 和 reject

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

①resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”或者"失败"(即从Pending变为Resolved或者Rejected),在异步操作成功或者失败时调用,并将异步操作的结果,作为参数传递出去;
②reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

注意: 如果传给 resolve(…) 的是一个非 Promise、非 thenable 的立即值,这个 promise 就会用这个值完成。如果传给 resolve(…) 的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的)promise 将取用其最终决议值或状态。reject(…) 不会像 resolve(…) 一样进行展开。 如 果 向reject(…) 传入一个 Promise/thenable 值,它会把这个值原封不动地设置为拒绝理由。

var promise = new Promise(function(resolve, reject) {
	// ... some code
	if (/* 异步操作成功 */){
	  resolve(value);
	} else {
	  reject(error);
	}
});
//Promise(..) 构造器的第一个参数回调会展开 thenable(和 Promise.resolve(..) 一样)或真正的 Promise
var rejectedPr = new Promise( function(resolve,reject){ 
	// 用一个被拒绝的promise完成这个promise 
	resolve( Promise.reject( "Oops" ) ); //这里调用resolve将会进入失败状态
} ); 
rejectedPr.then( 
	function fulfilled(){ 
		// 永远不会到达这里
	}, 
	function rejected(err){ 
		console.log( err ); // "Oops" 
	} 
);

Promise.resolve(…): resolve(决议)是一个精确的好名字,因为它实际上的结果可能是完成或拒绝。

var fulfilledTh = { 
	then: function(resolve) { resolve( 42 ); } 
}; 
var rejectedTh = { 
	then: function(resolve,reject) { 
		reject( "Oops" ); 
	} 
}; 
var p1 = Promise.resolve( fulfilledTh ); 
var p2 = Promise.resolve( rejectedTh ); 
// p1是完成的promise
// p2是拒绝的promise

Promise.reject(…): 创建一个已被拒绝的 Promise.

//以下两个promise 是等价的:
var p1 = new Promise( function(resolve,reject){ 
	reject( "Oops" ); 
} ); 
var p2 = Promise.reject( "Oops" );

Promise.resolve与Promise.reject的异同
相同点:

如果传给Promise.resolve(…)/ Promise.reject(…) 的是一个非 Promise、非 thenable的立即值,这个 promise 就会用这个值完成/拒绝。

不同点:

Promise.resolve(…) 会递归展开 thenable 值,但Promise.reject不会展开,会将原值设为拒绝理由。
如果传入的是真正的 Promise,Promise.resolve(…) 什么都不会做,只会直接把这个值返回,是不会有额外的开销的。而Promise.reject会将Promise原值设为拒绝理由返回。

(四)then的用法(Fullfilled与Rejected)

Promise实例生成以后,可以用then方法分别指定Fullfilled状态和Reject状态的回调函数。

then方法可以接受两个回调函数作为参数。
第一个回调函数是Promise对象的状态变为Fullfilled时调用,毫无疑义,总是处理完成的情况,所以不需要使用标识两种状态的术语“resolve”。
第二个回调函数是Promise对象的状态变为Reject时调用。
其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

注意:

1)如果完成或拒绝回调中抛出异常,返回的 promise 是被拒绝的。
2)如果任意一个回调返回非 Promise、非 thenable 的立即值,这个值会被用作返回 promise 的完成值。
3)如果完成处理函数返回一个 promise 或 thenable,那么这个值会被展开,并作为返回promise 的决议值。

promise.then(function fulfilled(value) {
  // success
}, function rejected(err) {
  // failure
});

(五)catch的用法

catch(…) 只接受一个拒绝回调作为参数,并自动替换默认完成回调。换句话说,它等价于 then(null,…)。

p.catch( rejected ); // 或者p.then( null, rejected )

(六)Promise的链式流

链式流程控制可行的 Promise 固有特性:
①每次你对 Promise 调用 then(…),它都会创建并返回一个新的 Promise,我们可以将其链接起来;
②在完成(fullfilled)或拒绝(rejected)处理函数内部,如果返回一个值或抛出一个异常,新返回的(可链接的)Promise 就相应地决议(走fullfilled或走rejected)。

// Promise-aware ajax 
function request(url) { 
	return new Promise( function(resolve,reject){ 
		// ajax(..)回调应该是我们这个promise的resolve(..)函数
		ajax( url, resolve ); //下面的例子基于 在ajax回调中调用了resolve函数
	} ); 
}
// 步骤1:
request( "http://some.url.1/" ) 
// 步骤2:
.then( function(response1){ 
	foo.bar(); // undefined,出错!
	// 永远不会到达这里
	return request( "http://some.url.2/?v=" + response1 ); 
} ) 
// 步骤3:
.then( 
	function fulfilled(response2){ 
	// 永远不会到达这里
	}, 
	// 捕捉错误的拒绝处理函数
	function rejected(err){ 
		console.log( err ); 
		// 来自foo.bar()的错误TypeError 
		return 42; 
	} 
) 
// 步骤4:
.then( function(msg){ 
	console.log( msg ); // 42 
} );

③如果完成或拒绝处理函数返回一个 Promise,它将会被展开,这样一来,不管它的决议值是什么,都会成为当前 then(…) 返回的链接 Promise 的决议值。

var p = Promise.resolve( 21 ); 
p.then( function(v){ 
console.log( v ); // 21 
// 创建一个promise并返回
return new Promise( function(resolve,reject){resolve( v * 2 ); } ); }) 
.then( function(v){ 
	console.log( v ); // 42 
} );

Promise链式调用的一些特例:

/*我们可以构建这样一个序列:不管我们想要多少个异步步骤,每一步都能够根据需要等待下一步.*/
function delay(time) { 
	return new Promise( function(resolve,reject){setTimeout(resolve,time);} ); 
} 
delay( 100 ) // 步骤1 
.then( function STEP2(){ 
	console.log( "step 2 (after 100ms 运行)" ); 
	return delay( 200 ); 
} ) 
.then( function STEP3(){ 
	console.log( "step 3 (after another 200ms 运行)" ); 
} )

/*如果没有给then(..)传递一个适当有效的函数作为完成处理函数参数,还是会有作为替代的一个默认处理函数*/
var p = Promise.resolve( 42 ); 
p.then( 
	// 假设的完成处理函数,如果省略或者传入任何非函数值
	// fullfilled函数置为 null 等价于 function(v) {return v; } 
	null, 
	function rejected(err){ 
		// 永远不会到达这里
	} 
)
.then(
	function fullfilled(msg){ 
		console.log(msg)//42
	} ,
	function rejected(err){ 
		console.log(err)//不会运行到这里
	} 
);;

(七)Promise对比回调机制

回调方式:

function add(getX,getY,cb) { 
	var x, y; 
	getX( function(xVal){ 
		x = xVal; 
		//对异步函数做的兼容处理(有可能还没取到值)
		if (y != undefined) { 
			send( x + y ); // 发送求和结果
		} 
	} ); 
	getY( function(yVal){ 
		y = yVal; 
		// 两个都准备好了?
		if (x != undefined) { 
			send( x + y ); // 发送求和结果
		} 
	} ); 
} 
// fetchX() 和fetchY()是同步或者异步函数
add( fetchX, fetchY, function(sum){ 
	console.log( sum );
} );

为了能够顺利执行发送求和结果,需要添加特定的逻辑来做一些统一的异步处理。
Promise:

function add(xPromise,yPromise) { 
	// Promise.all([ .. ])接受一个promise数组并返回一个新的promise,这个新promise等待数组中的所有promise完成
	return Promise.all( [xPromise, yPromise] ) 
	// 这个promise决议之后,我们取得收到的X和Y值并加在一起
	.then( function(values){ 
		// values是来自于之前决议的promise的消息数组
		return values[0] + values[1]; 
	} ); 
} 
// fetchX()和fetchY()返回相应值的promise,可能已经就绪,也可能以后就绪
add( fetchX(), fetchY() ) 
// 我们得到一个这两个数组的和的promise
// 现在链式调用 then(..)来等待返回promise的决议
.then( function(sum){ 
	console.log( sum ); 
} );

promise 归一保证了行为的一致性。我们可以按照不依赖于时间的方式追踪值 X 和 Y。它们是未来值。

(八)Promise完成事件

考虑如下代码,foo是一个Promise:

function foo(x) { 
	// 可是做一些可能耗时的工作
	// 构造并返回一个promise
	return new Promise( function(resolve,reject){ 
		// 最终调用resolve(..)或者reject(..)
		// 这是这个promise的决议回调
	} ); 
} 
var p = foo( 42 ); 

方式一:不论 foo(…) 成功与否,bar(…),baz(…) 都会被调用。并且如果收到了foo(…) 失败的通知,它会亲自处理自己的回退逻辑。

bar( p ); 
baz( p );
function bar(fooPromise) { 
	// 监听foo(..)完成
	fooPromise.then( 
		function(){ 
			// foo(..)已经完毕,所以执行bar(..)的任务
		}, 
		function(){ 
			// 啊,foo(..)中出错了!
		} 
	); 
}
// 对于baz(..)也是一样

方式二:bar(…)、baz(…) 只有在 foo(…) 成功时才会被调用,否则就会调用 oppsBar(…),oppsBaz(…)。

function bar() { 
	// foo(..)肯定已经完成,所以执行bar(..)的任务
} 
function oopsBar() { 
	// 啊,foo(..)中出错了,所以bar(..)没有运行
} 
// 对于baz()和oopsBaz()也是一样
var p = foo( 42 ); 
p.then( bar, oopsBar ); 
p.then( baz, oopsBaz );

两段代码都以使用 promise p 调用 两次then(…) 结束(bar、baz)。这个事实说明了前面的观点, 就是Promise(一旦决议)一直保持其决议结果(完成或拒绝)不变,可以按照需要多次 查看。

(九)Promise的优缺点

优点:
1)有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
2)一旦 Promise 决议,它就永远保持在这个状态。此时它就成为了不变值(immutable value),可以根据需求多次查看。
3)Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点:
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Funnee

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

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

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

打赏作者

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

抵扣说明:

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

余额充值