promise详解一(解读Promise对象)

为什么需要Promise?

Javascript是一门单线程语言,早期我们解决异步场景时,大部分情况都是通过回调函数来进行。
例如,在浏览器中发送ajax请求,就是常见的异步场景。发送请求后,一段时间服务端相应之后我们才能拿到结果,如果我们希望在异步结束之后执行某个操作,就只能通过回调函数这样的方式进行操作。

var dynamicFunc = function(cb){
	setTimeout(function(){
		cb();
	}, 1000);
}

dynamicFunc(function() {console.log(123)});

例如以上示例中的dynamicFunc就是一个异步函数,里面执行的setTimeout会在1S后调用传入的cb函数。
按照上面的调用方式,最终1S后,会打印出123这个结果。
同样的,如果后续还有内容需要在异步函数结束时输出的话,就需要多个异步函数进行嵌套,非常不利于后续的维护。

setTimeout(function() {
	console.log(123);
	setTimeout(function() {
		console.log(222);
		// ...
	}, 2000);
}, 1000);

为了能使回调函数以更优雅的方式进行调用,在ES6中产生了一个新的规范–Promise。

Promise的含义

  • 简单的说,promise就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
  • Promise内部包含三种状态:pending(进行中)、fulllfilled(已成功)、rejected(已失败)。
    只有异步操作的结果,可以决定当前是哪一种状态,其他任何操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
    Promise对象的状态改变只会有两种可能:从pending变为fullfilled,并将异步操作的结果作为参数传递出去,或者从pending变为rejected,将异步操作报出的错误作为参数传递出去。
    只要这两种情况发生,状态就凝固了,这时成为resolved(已完成)。Promise的状态一但resolved这时如果再对Promise对象添加回调,也会立即得到这个结果。

接下来,我们通过console.dir(Promise)看看Promise到底是什么?
console.dir(Promise)
可以看出Promise是一个构造函数,其自身有resolve,reject,race,all等方法,而其原型上则有catch、finally、then等方法,接下来我将一一试验各方法的具体用法。

Promise的用法

// 示例一
var promise1 = new Promise(function(resolve, reject){
	// 此处定义异步操作的内容
	setTimeout(function() {
		console.log('1s后输出');
		return resolve();
	}, 1000);
});
// 1S后输出

需要注意的是,Promise内的参数函数是立即执行的。以上示例只是创建了一个Promise对象,就会在1S后打印出“1S后输出”。因此用Promise的时候,最好将其包裹在一个函数内,需要使用时再进行调用,示例如下:

// 示例2
function promise2() {
	return new Promise(function(resolve){
		setTimeout(function() {
			console.log('2s 后输出');
			resolve();
			console.log('仍然会输出');
		}, 2000);
	});
}
promise2(); // '2s 后输出'  	'仍然会输出'

需要注意的是,resolve或reject并不会终止Promise的参数函数的执行。因此,promise2执行时会先后打印’2s后输出‘和’仍然会输出‘。一般来说,resolve或者reject之后,Promise的任务已经完成,其后的操作应放到then方法,而不应该放在resolve或reject后面,所以最好在resolve或reject前面加上return,防止意外。

在示例2中可以看出,promise如果状态变为已完成,那么就会立即执行then方法中的下一个promise函数。同样的,如果promise变为已拒绝状态,那么就会进入后续的异常处理函数。示例如下:

// 示例3
function promise3() {
	return new Promise(function(resolve, reject){
		var random = Math.random() * 10;
		setTimeout(function() {
			if (random > 5) {
				return resolve(random);
			} else {
				return reject(random);
			}
		}, 3000);
	});
}

var onResolve = function(val) {
	console.log('已完成,结果为:' + val);
}

var onReject = function(val) {
	throw new Error('已拒绝,结果为:' + val);
}

// promise的then方法也可以接收两个函数作为参数,第一个参数为resolve后执行,第二个参数为reject后执行
promise3().then(onResolve, onReject);

示例3中,若随机数大于5,则执行resolve将Promise状态改为fullfilled,并将随机数传递至处理函数,若随机数小于5,则执行reject将Promise状态改为rejected,并将随机数作为原因传递至处理函数。

Promise方法解析

1. then

从我们打印的Promise内容来看,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。从示例3可看出,then方法的第一个参数是fullfilled状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

// 示例4
promise2()
.then(promise3)
.then(function(){
	// promise3的状态变为fullfilled后
	onResolve();
}, function(){
	// promise3的状态变为rejected后
	onReject();
}

示例4中,promise2先执行,2s后输出“2S后输出“、”依然会输出“,其回调函数(第一个.then)返回了promise3,由于promise3也是Promise对象(包含异步操作),因此第二个.then会等promise3状态变化后执行,即再3s后输出Promise3的回调处理结果。

2. catch

从then的用法中我们可以看出,then方法的第二个参数可以作为状态变为rejected的处理函数。
catch方法的作用与then方法第二个参数的作用一致。

// 通过catch方法拦截状态变为已拒绝时的Promise
promise3().then(onResolve).catch(onReject);
// 等同于
promise3().then(onResolve).then(null, onReject);

// 也可以通过try catch进行拦截状态变为已拒绝的promise
try{
	promise3().then(onResolve);
} catch (e) {
	onReject(e);
}

总结一下,我们可以用三种方式拦截rejected状态的promise,分别是使用then的第二个参数使用catch方法捕获前方promise抛出的异常使用try catch拦截promise抛出的异常

另外,then方法中的回调函数抛出的错误,也会被catch捕获, catch方法也可以抛出错误,错误可以被后面的catch方法捕获,示例如下:

//示例5
const promise = new Promise(function(resolve, reject) {
  resolve();
  throw new Error('不会被捕获');
});
promise.then(()=>{
  throw new Error('出错了');
}).catch(function(error) {
  console.log(error);
  throw new Error('出错了2');
}).catch(function(err){
  console.log(err)
 });
// Error: 出错了
// Error: 出错了2

resolve()之后抛出的错误不会被捕获。 因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。因此,已办来说不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// 示例6
const promise = new Promise(function(resolve, reject){
  throw new Error('错误1');
})
promise.then(()=>{
  console.log('resolve1');
  resolve();
}).then(()=>{
  console.log('resolve2');
  resolve();
}).catch((e)=>{
  console.log(e);
})
// Error: 错误1

值得注意的是:如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

// 示例7
const test1 = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};
test1().then(function() {
  console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
// UnhandledPromiseRejectionWarning: ReferenceError: x is not defined

// 作为对比,请看下一个示例
const test2 = function() {
  return x + 2;
};
test2();
setTimeout(() => { console.log(123) }, 2000);
// Uncaught ReferenceError: x is not defined

在示例7中,test1会抛出ReferenceError, Promise的回调中并未设置then方法的第二个参数或用catch拦截,因此无法处理该错误,但该错误不会对外层代码造成影响(不会退出进程、中断脚本执行),setTimeout会继续执行,打印出123。
而test2同样抛出了ReferenceError,此错误会中断脚本执行。
在nodeJS环境运行时,test1会产生UnhandledPromiseRejectionWarning的错误提示。这是因为Node.js 有一个unhandledRejection事件,专门监听未捕获的reject错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。

process.on('unhandledRejection', function (err, p) {
  throw err;
});

3. all

all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,并且在所有异步操作执行完后才执行回调

const promise = Promise.all([p1, p2, p3]);

promise的状态由p1,p2,p3共同决定。

  • 当p1,p2, p3的状态都为fullfilled时,promise的状态为fullfilled,其回调函数的参数是p1、p2、p3的返回值构成的数组。
  • 当p1,p2,p3中任一状态变为rejected时,promise状态为rejected,第一个被reject的实例的返回值,会传递给promise的回调函数
const promise1 = new Promise(function(resolve, reject){
  var random = Math.random() * 10;
  setTimeout(function() {
    if (random > 5) {
      return resolve('已完成1:' + random);
    } else {
      return reject('已拒绝1:' + random);
    }
  }, random * 1000);
});

const promise2 = new Promise(function(resolve, reject){
  var random = Math.random() * 10;
  try{
	  setTimeout(function() {
	    if (random < 5) {
	      return resolve('已完成2:' + random);
	    } else {
	      throw new Error('已拒绝2:' + random);
	    }
	  }, random * 1000);
  } catch(err){
  		reject(err)
 }
});
Promise.all([promise1, promise2]).then(function(res){
  console.log(res)
}).catch(function(err){
  console.log(err)
});
// promise1的随机数大于5,且promise2的随机数小于5时,promise的状态才为fullfilled,打印如下
// [ '已完成1:9.549173298650732', '已完成2:1.8658475409956' ]   
// 否则,打印如下
// 已拒绝1:0.33950819911513275      或		Error: 已拒绝2:6.320498009433047

4. race

race方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const promise = Promise.race([p1, p2, p3]);

promise的状态由p1,p2,p3共同决定。当p1,p2, p3的状态有一个发生改变时,promise的状态就跟着发生改变,率先改变状态的Promise实例的返回值,作为参数传递给promise的回调函数。需要注意的是,假设p1状态变为fullfilled,promise的状态也会变成fullfilled并执行对应的回调函数,但是p2、p3内参数函数依然会运行,其运行状态的改变不会影响promise的状态,因此promise的回调函数也不会被再次触发。

Promise.race([promise1, promise2]).then((res)=>{
	console.log(res)
}).catch((err)=>{
	console.log(err)
})
// 可能打印的结果如下
// 已拒绝1:4.263485091142729
// 已完成1:5.242984072464056
// 已完成2:0.26877623990574806
// Error: 已拒绝2:7.478026943737685

5. finally

finally方法用于指定不管Promise对象的最后状态是什么,都会执行的操作。finally方法的回调函数不接收参数,这意味着无法知道上一个Promise的状态,也无法改变上一个Promise的状态和结果。这说明,finally方法的操作应该是与状态无关的,其本质上是then方法的特例。

Promise.race([promise1, promise2]).finally(()=>{
	// ...语句
})
// 等同于
Promise.race([promise1, promise2]).then((res)=>{
	// ...语句
	return res	// 将Promise的resolve值原样返回
}, (err)=>{
	// ...语句
	return err	// 将Promise的reject理由原样返回
})

对比一下then方法和finally方法的区别:

Promise.resolve(1).then(()=>{}, ()=>{});	// Promise {<resolved>: undefined}
Promise.resolve(1).finally(()=>{});			// Promise {<resolved>: 1}
Promise.reject(2).then(()=>{}, ()=>{});		// Promise {<resolved>: undefined}
Promise.reject(2).finally(()=>{});			// Promise {<resolved>: 2}

6. resolve

resolve()方法返回的是一个新的Promise实例。

  • 当resolve参数是一个Promise实例时,resolve将原封不动的返回这个实例。
  • 当resolve参数是一个包含then方法的对象时,resolve将封装这个对象为Promise实例,并立即执行这个对象的then方法。
  • 当resolve参数不包含then方法或不是对象,resolve将返回一个状态为fullfilled的新的Promise对象。
  • 当resolve不含参数时,返回一个fullfilled状态的 Promise 对象。

7.reject

reject()方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

8. allSettled

9. any

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值