JS的promise以及promise链使用详解

异步操作通常都带着回调函数以便异步动作完成时操作,这种设计本身没什么问题,但是一旦出现了异步的嵌套就会让人陷入回调地狱,层层嵌套的操作让人看着眼花缭乱。Promise结构的出现就是专门帮助我们解决这个烦恼的,这一节我们就来看看promise的用法以及promise链。

Promise的基本结构

首先必须要理解,promise并不是一个具体的异步操作(例如Ajax或者setTimeout),而只是通过一种新的书写格式,将原来的异步操作和回调函数部分进行了解耦合,更容易让人理解。所以promise并不是异步操作,而只是对异步操作的重新包装

解耦合之后的异步操作部分,可以理解为生产者,而异步操作的结果(不管成功或者失败)会被传递给一个或多个回调函数,可以理解为消费者。下面我们就分别来看看生产者和消费者的书写格式。

异步操作部分

就是新建一个Promise对象,基本书写格式如下

let promise = new Promise(function(resolve, reject) {
  // 异步操作的代码
});

创建promise的时候要传递一个函数进去,该函数包含的部分会被马上执行。注意函数的两个参数resolvereject,是js规定好的两个回调函数,分别是当函数成功返回或者报错时候进行调用。所以函数的执行结果不需要return,而只能是下面两种中的一个

  • resolve(value) - 函数成功执行,传递value到下一步
  • reject(error) - 函数执行失败,传递error到下一步

如下图所示

1-promise.png

下面就是成功返回结果的例子

let prom = new Promise(function(resolve,reject){
		console.log(1);
		setTimeout(function(){
			console.log(2);
			resolve('Done')
		},2000)
});
console.log(3);

这里通过setTimeout模仿一个耗时2秒钟执行完成的异步动作。程序直接执行promise内的函数,所以先打印1,之后将异步操作交给专门的进程去处理,主进程继续向下执行,打印3,过了两秒打印2并且返回一个Done的结果给后面去使用。所以最后的打印顺序就是1、3、2。

如果将resolve('Done')改为reject(new Error("Whoops!"))就是返回一次失败的异步操作,其中的Error对象也可以供后面去使用。

这样就可以在异步操作中对结果做一个初步的判断,例如Ajax操作获取数据,如果数据符合预期就用resolve方法传递数据到下一步,否则就用reject方法传递错误到下一步。

只有第一次resolve或者reject才会生效

注意如果是像下面这样子,只会传递Done到下一步,后面的不管成功或者失败的返回都会被忽略

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // ignored
  setTimeout(() => resolve("…")); // ignored
});

resolve或者reject之后还是可以执行命令

函数中的return代表着函数返回,并不会继续向下执行。而和函数中return不同,promise中resolve或者reject并不会影响函数继续向下执行

let prom = new Promise(function(resolve,reject){
		console.log(1);
		setTimeout(function(){
			console.log(2);
			resolve('Done');
			console.log(4);
		},2000)
});
console.log(3);

上面的脚本会打印

1
3
2
4

并且,后面就会看到,4的打印要比后面的消费者处理Done要快,这涉及到js中的异步操作和事件循环逻辑,这里不展开了,我会专门写一篇博客来详细说明。

消费者

前面异步操作返回的结果,不管是成功或错误,都会传递到下一步的消费函数。要声明一个消费函数,可以用promise对象的.then.catch或者是.finally方法。

then

then算是最常用的方法,格式如下

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

传递两个函数作为参数,第一个是异步操作resolve时执行,第二个是异步操作reject时执行。例如

let prom = new Promise(function(resolve,reject){
	console.log(1);
	setTimeout(function(){
		console.log(2);
		resolve('Done');
		// reject(new Error('Whoops'));
        console.log(4);
	},2000)
});
console.log(3);
prom.then(result=>console.log(result),error=>console.log(error));

此时会打印

1
3
2
4
Done

再次说明,为什么Done在4后面涉及到js的事件循环逻辑,这里不展开了,后面再专门写一篇来详细说明

因为是resolve,所以第一个函数被执行,如果换成reejct,就是第二个函数被执行。

如果只是对成功返回的结果感兴趣,可以只保留第一个函数

let prom = new Promise(function(resolve,reject){
	console.log(1);
	setTimeout(function(){
		console.log(2);
		resolve();
		// reject(new Error('Whoops'));
        console.log(4);
	},2000)
});
console.log(3);
prom.then(result=>console.log('Xiaofu'));

最后会打印

1
3
2
4
Xiaofu

注意我这里也顺便把resolve的参数去掉了,也就是说resolve和reject可以只传递一个成功或失败的信号,而不带任何内容到下一步

catch

如果只是对失败的结果感兴趣,可以将then方法的第一个参数设置为null,或者使用catch方法

let prom = new Promise(function(resolve,reject){
	console.log(1);
	setTimeout(function(){
		console.log(2);
		// resolve();
		reject();
		console.log(4);
},2000)
});
console.log(3);
prom.then(()=>{console.log('Xiaofu')});
prom.catch(()=>{console.log('Oh no!')});

会打印

1
3
2
4
Oh no!
finally

try{}catch{}语法一样,promise也有一个finally方法,不管promise的函数返回成功或者失败,finally都会被执行。其特别适合做一些善后的工作,例如记录操作结果进日志之类的。

Promise链

可以看到,promise的生产消费的结构可以非常直观地将异步操作展现出来。那么如果出现了回调函数的嵌套呢?这时候就需要用来promise链(promise chaining)了。

类似下面这样

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

promise链其实就是then方法的级联,前一个then方法中函数返回的结果会传递到下一个then方法

  • 首先promise函数在一秒后resolve为1 (*)
  • 第一个then中函数被调用,打印1,并且返回2 (**)
  • 下一个then中函数被调用,打印上面then传递的2,并且返回4 (***)
  • 依次类推,最后的结果就是1->2->4

之所以能这样使用,是因为then方法返回的结果还是一个promise对象,而其函数return的值就是promise所resolve的内容。

注意,如果是对同一个promise对象多次使用then方法并不是promise链

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

这样相当于对promise增加了3个消费者,所以最后只会弹出3个1。

用图形来表示的话,promise链中的数据流是这样的

2-chain.png

而作为对比,上面错误的使用数据流是这样的

3-wrong.png

像这种一个promise对象添加多个then方法的情况在实际使用中很少,往往是promise链这种回调函数的嵌套比较常见。

返回promise对象

在promise链中,then方法中的函数可以显示地去返回promise对象,如下

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

这样写的返回结果和前面的例子一样,也是1->2->4,但是每次返回之间会隔着1秒钟的间隔。这样的好处就是我们可以进行多个异步操作的级联

总结

总结下这节的知识点

  • promise本身不是异步操作,而只是异步操作的一种更直观的书写格式
  • promise对象在创建时候里面的函数会被马上执行,并且最终的结果是resolve或者reject的一种
  • resolve和reject的3个特性:只有第一个才有效,可以不带参数,之后的代码还会执行
  • 通过promise对象的then、catch和finally方法来使用resolve和reject传递的内容
  • 通过then方法的级联来实现promise链,还可以通过显示地返回promise对象来实现多个异步操作的级联
  • 需要注意的是对同一个promise对象的多次then方法并不是promise链

参考

  • promise - https://javascript.info/promise-basics
  • promise chaining - https://javascript.info/promise-chaining

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值