promise构造时的resolve方法执行了来触发then执行吗?

30分钟,让你彻底明白Promise原理

前一阵子记录了promise的一些常规用法,这篇文章再深入一个层次,来分析分析promise的这种规则机制是如何实现的。ps:本文适合已经对promise的用法有所了解的人阅读,如果对其用法还不是太了解,可以移步我的上一篇博文。

本文的promise源码是按照Promise/A+规范来编写的(不想看英文版的移步Promise/A+规范中文翻译)

为了让大家更容易理解,我们从一个场景开始讲解,让大家一步一步跟着思路思考,相信你一定会更容易看懂。

考虑下面一种获取用户id的请求处理

getUserId方法返回一个promise,可以通过它的then方法注册(注意注册这个词)在promise异步操作成功时执行的回调。这种执行方式,使得异步调用变得十分顺手。

那么类似这种功能的Promise怎么实现呢?其实按照上面一句话,实现一个最基础的雏形还是很easy的。

上述代码很简单,大致的逻辑是这样的:

  1. 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
  2. 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;

可以结合例1中的代码来看,首先new Promise时,传给promise的函数发送异步请求,接着调用promise对象的then属性,注册请求成功的回调函数,然后当异步请求发送成功时,调用resolve(results.id)方法, 该方法执行then方法注册的回调数组。

相信仔细的人应该可以看出来,then方法应该能够链式调用,但是上面的最基础简单的版本显然无法支持链式调用。想让then方法支持链式调用,其实也是很简单的:

see?只要简单一句话就可以实现类似下面的链式调用:

细心的同学应该发现,上述代码可能还存在一个问题:如果在then方法注册回调之前,resolve函数就执行了,怎么办?比如promise内部的函数是同步函数:

这显然是不允许的,Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve执行之前,then方法已经注册完所有的回调。我们可以这样改造下resolve函数:

上述代码的思路也很简单,就是通过setTimeout机制,将resolve中执行回调的逻辑放置到JS任务队列末尾,以保证在resolve执行时,then方法的回调函数已经注册完成.

但是,这样好像还存在一个问题,可以细想一下:如果Promise异步操作已经成功,这时,在异步操作成功之前注册的回调都会执行,但是在Promise异步操作成功这之后调用的then注册的回调就再也不会执行了,这显然不是我们想要的。

恩,为了解决上一节抛出的问题,我们必须加入状态机制,也就是大家熟知的pending、fulfilled、rejected。

改进后的代码是这样的:

上述代码的思路是这样的:resolve执行时,会将状态设置为fulfilled,在此之后调用then添加的新回调,都会立即执行。

这里没有任何地方将state设为rejected,为了让大家聚焦在核心代码上,这个问题后面会有一小节专门加入。

那么这里问题又来了,如果用户再then函数里面注册的仍然是一个Promise,该如何解决?比如下面的例4:

这种场景相信用过promise的人都知道会有很多,那么类似这种就是所谓的链式Promise。

下面来看看这段暗藏玄机的then方法和resolve方法改造代码:

//如果then中没有传递任何东西

我们结合例4的代码,分析下上面的代码逻辑,为了方便阅读,我把例4的代码贴在这里:

  1. then方法中,创建并返回了新的Promise实例,这是串行Promise的基础,并且支持链式调用。

更直白的可以看下面的图,一图胜千言(都是根据自己的理解画出来的,如有不对欢迎指正):

在异步操作失败时,标记其状态为rejected,并执行注册的失败回调:

有了之前处理fulfilled状态的经验,支持错误处理变得很容易,只需要在注册回调、处理状态变更上都要加入新的逻辑:

上述代码增加了新的reject方法,供异步操作失败时调用,同时抽出了resolve和reject共用的部分,形成execute方法。

错误冒泡是上述代码已经支持,且非常实用的一个特性。在handle中发现没有指定异步操作失败的回调时,会直接将bridge promise(then函数返回的promise,后同)设为rejected状态,如此达成执行后续失败回调的效果。这有利于简化串行Promise的失败处理成本,因为一组异步操作往往会对应一个实际功能,失败处理方法通常是一致的:

细心的同学会想到:如果在执行成功回调、失败回调时代码出错怎么办?对于这类异常,可以使用try-catch捕获错误,并将bridge promise设为rejected状态。handle方法改造如下:

如果在异步操作中,多次执行resolve或者reject会重复处理后续回调,可以通过内置一个标志位解决。

刚开始看promise源码的时候总不能很好的理解then和resolve函数的运行机理,但是如果你静下心来,反过来根据执行promise时的逻辑来推演,就不难理解了。这里一定要注意的点是:promise里面的then函数仅仅是注册了后续需要执行的代码,真正的执行是在resolve方法里面执行的,理清了这层,再来分析源码会省力的多。

现在回顾下Promise的实现过程,其主要使用了设计模式中的观察者模式:

  1. 被观察者管理内部pending、fulfilled和rejected的状态转变,同时通过构造函数中传递的resolve和reject方法以主动触发状态转变和通知观察者。
}

今天发现一个问题,看下方代码

  • 我的任务就是项目统计. 1 效益统计 1 教育效益统计表 (教育效益统计表,增,改,查看,查) 2 农牧林效益统计表 (农牧林效益统计表,增,改,查看,查) 3 乡村效益统计表    (乡村效益统计表 ...

  • 还记得大学毕业刚工作的时候是做flash的开发,那时候看到别人写的各种各样的UI组件就非常佩服,后来自己也慢慢尝试着写,发现其实也就那么回事.UI的开发其实技术的成分相对来说不算多,但是一个好的UI是 ...

  • 一 . Java爬取B站弹幕 弹幕的存储位置 如何通过B站视频AV号找到弹幕对应的xml文件号 首先爬取视频网页,将对应视频网页源码获得 就可以找到该视频的av号aid=8678034 还有弹幕序号, ...

  • 概述 在这个信息急剧膨胀的社会,我们不得不说人类正进入一个崭新的时代,那就是信息时代.信息时代的一个主要而显著的特征就是计算机网络的应用.计算机网络从最初的集中式计算,经过了Client/Server ...

  • 06:空格分隔输出 总时间限制:  1000ms 内存限制:  65536kB 描述 读入一个字符,一个整数,一个单精度浮点数,一个双精度浮点数,然后按顺序输出它们,并且要求在他们之间用一个空格分隔. ...

}

  1. Promise是一门新的技术(ES6规范)
  2. Promise是JS中进行异步编程的新解决方案
    备注:旧的方案是单纯使用回调函数

  1. 从语法上来说:Promise是一个构造函数
  2. 从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功或失败的结果值。

Promise的状态指的是实例对象中的一个属性PromiseState,它的值有三个:

Promise的状态改变只有下面的三种情况:

说明:只有这2种,且一个Promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为value,失败的结果数据一般称为reason

Promise实例对象中还有一个属性PromiseResult,保存着对象成功/失败的结果。要想改变它的值,只有通过给resolve()或者reject()方法传参,成功时,将对应数据传入resolve()方法,失败时,将对应的数据传入reject()方法,之后可以在then(value

需求:点击按钮,1秒后显示是否中奖(30%概率中奖)。若中奖,弹出:恭喜恭喜,奖品为10万RMB玛莎拉蒂优惠券;若未中奖弹出:再接再厉。

html页面,放个标题和按钮即可:

* 点击按钮, 2s 后显示是否中奖(30%概率中奖) * 若中奖,弹出:恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券 * 若未中奖弹出: 再接再厉 // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了(简易的算法)
// resovle 解决 是函数类型的数据 异步任务成功时调用 // reject 拒绝 是函数类型的数据 异步任务失败时调用 // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了 // then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用

现在需求变更,在弹出提示时,同时告知本次抽到的数字。

// 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了 alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券,您的中奖数字为:' + n);
// 如果现在需要在弹出提示时,同时告知本次的号码,该怎么办? // 只需要将成功或失败的值分别传入 resolve() 和 reject() 方法,然后再then()方法的两个回调函数中接收即可。 // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了 // then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用

指定回调函数的方式更加灵活

  1. 旧的:必须在启动异步任务前指定
  2. promise:启动异步任务=> 返回promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定一个或多个)

支持链式调用,可以解决回调地狱问题

回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件

例如,下面的代码是Node.jsfs模块读取三个文件,并将读取到的结果进行拼接并且输出到控制台中。

这个代码片段,fs.readFile()在嵌套调用,这里就形成了回调地狱。

  1. resolve函数:内部定义成功时我们调用的函数
  2. reject函数:内部定义失败时我们调用的函数

说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行。

说明:指定用于得到成功value的成功回调和用于得到reason的失败回调,返回一个新的Promise对象。

catch()只能指定失败的回调,不能指定成功的回调。其实也是使用then()实现的.

说明:返回一个成功/失败Promise对象

// 如果传入的参数为 非Promise类型的对象,返回的结果为 成功的Promise对象 // 如果传入的参数为 Promise对象,则参数的结果决定了resolve的结果 // 失败的同时还报错了,原因是没有对错误进行处理,这里使用catch()进行处理就能解决报错

说明:返回一个失败的Promise对象

// 总结,Promise.reject()方法,无论你传入什么得到的都是失败的Promise对象,并且失败的结果就是你传入的参数。

说明:返回一个新的Promise对象,只有所有的Promise都成功才成功,只要有一个失败了就直接失败,失败的结果就是失败的Promise对象的结果。

all()方法的示例:

// 如果数组中 p2 是一个失败的Promise,那么all()方法返回的是一个失败的Promise对象,并且失败的结果是数组中失败的Promise对象的结果。

说明:该方法返回的结果总是状态为成功的Promise对象,这个Promise对象的结果值是包含promises参数中每一个promise对象的状态以及结果值的对象数组。

注意和all()方法的区别。

// 它的结果值是包含每一个promise对象的状态以及结果值的对象。

说明:返回一个新的Promise对象,第一个完成的Promise对象的结果状态就是最终的结果状态

// 代码从上往下执行,所以p1最先执行,所以result的状态和结果应该是p1的状态和结果。 // p4为异步代码,p5 会先于p4、p6执行,所以p5的状态和结果即为result2的状态和结果。

如何改变Promise的状态?

改变Promisede状态有三种方法:

一个Promise指定多个成功/失败的回调函数,都会调用吗?

指定回调用的就是then(),所以这个题目的意思是如果使用then()方法为一个promise对象指定多个回调,那么这些回调是否都会执行?

答案是:当Promise改变为对应状态时都会调用。

// 有弹框,控制台有输出,说明这两个回调都执行了。

改变Promise状态和指定回调函数谁先执行谁后执行?

  1. 都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
  2. 如何先改状态再指定回调?
  • then()方法延迟更长时间才被调用

3. 什么时候才能得到数据?

    • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
    • 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
// 1. 当Promise构造函数中的executor执行器函数内部是同步任务时,先改变状态,在执行then()方法指定回调(注意是指定回调,不是执行回调) // 2. 当Promise构造函数中的executor执行器函数内部是异步任务时,那么是先执行then()方法指定回调函数,再改变状态。

  1. 简单表达:由then()指定的回调函数执行的结果决定
  • 如果返回的是另一个新promise,此promise的结果就会成为新promise的结果

promise如何串连多个操作任务?

  1. 通过then的链式调用串连多个同步或异步任务

  1. 当使用promisethen链式调用时,可以在最后指定失败的回调
  2. 前面任何操作除了异常,都会传到最后失败的回调中处理
// 在调用链的最后指定失败的回调,就可以处理任务当中出现的错误,这个现象称为异常穿透。

当使用promisethen链式调用时,想要在中间中断,不再调用后面的回调函数,有且只有一个办法:在回调函数中返回一个pending状态的promise对象。

// 如果想要中断这个then调用链,有且只有一个方式,在需要中断的回调函数中,返回一个pending状态的promise对象 // 假如后面不想让它调用了,有且仅有一个方式:返回一个pending状态的promise对象
}

我要回帖

更多关于 constructor在对象被new时执行 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信