最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端进阶 - Promise原理&宏微任务

    正文概述 掘金(非舟)   2021-01-17   555

    读完这篇文章,你的收获有:

    1. Promise简史
    2. Promise的关键概念
    3. 可以手写符合标准的Promise
    4. 可以解答任意宏任务/微任务的题目

    0. 前言

    为什么写这篇文章?

    JavaScript是异步语言,因此Promise的重要性不言而喻。

    而我看了一些文章,觉得质量参差不齐。

    于是就系统地整理了些资料,然后输出一篇文章,即帮助他人,也能让大家给我挑问题,避免自己错而不知。

    由于能力有限,文中可能存在错误,望广大网友指正。

    1. Promise简史

    Promise并不是一个新鲜的概念,早在2011年就出现在社区里了,目的是为了解决著名的回调地狱问题。

    这个概念是在JQuery Deferred Objects出现之后,开始流行的。并于2012年,Promise被提出作为规范:Promise/A+。

    在成为ES6标准之前,社区里也出现了许多符合Promise标准的库,如bluebird、q、when等等。

    2. Promise的关键概念

    Promise的基础认知,推荐看阮一峰的《ES6 入门教程》。

    本文的重点是讲解一些手写Promise需要关注的关键概念。

    2.1 Promise有三个状态:

    • pending
    • resolved
    • rejected

    只能从pending到resolved或rejected,之后状态就凝固了。

    前端进阶 - Promise原理&宏微任务

    当状态流转成resolved时,需要选择一个值作为当前Promise的value:

    • new Promise时,则是通过resolve(val)
    • promise.then时,则是通过return(需要注意的是,没有显式return时是默认return undefined

    这个值可以是任意的合法JavaScript值(包括undefinedthenable对象或者promise

    状态流转成rejected时,则需要用一个reason来作为当前Promise被reject的理由,和resolved时同理。

    2.2 Promise.prototype.then

    promise.then(onFulfilled, onRejected)
    
    • Promise/A+ 是Promise的标准规范,其中指出Promise实例只需要实现then一个方法
    • then接收两个参数,而两个参数都是可选的,意味着可以什么都不传
    • then是可以调用多次的。会按顺序调用,并且每次得到的promise状态和值都是相同的
    • 每次调用then均返回一个全新的Promise实例,这样就可以链式调用
    • then会在当前宏任务下形成一个微任务(具体介绍看下面)

    2.2.1 promise的状态

    then其实和Promise的构造函数是类似的,返回值都是一个新的Promise实例。

    它们之前的差异在于,通过构造函数生成的promise的状态,由构造函数自身决定:

    new Promise((resolve, reject) => {
    	resolve(1) // 将当前的状态流转成resolved
    })
    

    而then返回的promise的状态判断需要分两步走:

    1. then的回调函数能否处理上一个promise的状态,否则直接复用上一个promise的状态
    2. 若满足条件1,则看当前回调函数能否正常处理

    说得有点绕口,看下面的实例代码即可理解:

    理解条件1:

    let p1 = new Promise((resolve, reject) => { // Promise {<rejected>: "error1"}
    	reject('error1')
    })
    
    let p2 = p1.then(console.log) // Promise {<rejected>: "error1"}
    

    由于p1的状态是Rejected的,而p2没有传入onRejected的回调函数,因此p2的状态完全复用p1的状态。

    理解条件2:

    let p1 = new Promise((resolve, reject) => { // Promise {<fulfilled>: 1}
    	resolve(1)
    })
    
    let p2 = p1.then(val => { // Promise {<rejected>: ReferenceError: x is not defined}
    	console.log('p1 was resolved:', val)
    	return x; // Uncaught referenceError
    })
    
    let p3 = p2.then(undefined, reason => 1) // Promise {<fulfilled>: 1}
    

    p1的状态是fulfilled的,而p2onFulfilled的回调函数,但是没有正确处理,抛异常了。因此p2的状态变成了rejected,其中的reason为则报错的原因。

    而此时p3刚好有onRejected的函数,也能正确处理,最后的返回值则是自己的value,因此p3的状态是fulfilled的。

    2.2.2 promise的返回值

    前文也提到,promise的返回值可以是任意合法的JavaScript值,包括了promise,这里重点讲下。

    由于promise的返回值决定了当前promise的value,而value是其他的promise时,则说明value是未知的,依赖其他的promise的状态。

    同样看看例子:

    let p1 = new Promise(resolve => {
    	setTimeout(resolve, 1000, 1)
    }) 
    
    let p2 = new Promise(() => p1)
    

    p1是一个简单的定时器promise,在1000ms之后,状态会变成<fulfilled: 1>

    p2的返回值是p1,因此p2在1000ms之内也是<pending>,同样会在1000ms之后,变成<fulfilled: 1>

    2.3 Promise.prototype.catch

    虽然catch不是Promise/A+标准的方法,但是也需要提一下,因为这也是常用的方法之一。

    其实,catch可以理解成then的一种封装:

    promise.catch(function onRejected() {}) == promise.then(undefined, function onRejected() {})
    

    2.4 微任务 microtask

    当前promise的状态变更之后,不是立即执行then方法的。此时引入了 微任务(microtask) 的概念。

    与之对应的则是 宏任务(macrotask),基本的JavaScript代码则是在一个宏任务里执行的。

    也可以通过其他的方式生成宏任务:setTimeoutsetInterval;而微任务则可以通过promise.thenObject.observe(已废弃)MutationObserver生成。

    宏任务和微任务的关系则是这样的(此处引入winter老师在《重新前端》画的图):

    前端进阶 - Promise原理&宏微任务

    即一个宏任务下,是可以有多个微任务的。

    2.4.1 解析任务

    分析代码的时候,可以这样分几步走:

    1. 理想情况下,如果没有任何setTimeoutpromise.then的话,则全部在一个宏任务里执行
    2. 若出现promise.then,则在当前宏任务生成一个微任务,用于执行promise.then
    3. 若出现了setTimeout,则添加一个宏任务,重复条件1

    分析几个例子考验一下:

    例子1:

    setTimeout(console.log, 0, 0)
    
    new Promise((resolve) => {
        console.log(1)
        resolve(2)
    }).then(console.log)
    
    console.log(3)
    
    正确的输出顺序:

    1、3、2、0

    例子2:

    console.log(8)
    
    setTimeout(function() {
        console.log(0)
        Promise.resolve(4).then(console.log)
    }) // 省略参数,delay默认为0
    
    new Promise((resolve) => {
        console.log(1)
        resolve(2)
    }).then(console.log)
    
    console.log(3)
    
    setTimeout(console.log, 0, 5)
    
    正确的输出顺序:

    8、1、3、2、0、4、5

    其实,还有async/await相关的题目,如果阅读足够多的话,我再完善吧。

    3. 手写Promise

    其实,看到这里说明你已经掌握了几乎全部关键概念了。剩下的任务就是将这些逻辑翻译成代码。

    我在github写了一份,代码逻辑都算挺清晰的,大家可以去看看。

    我建议大家在写之前,再仔细看一下Promise/A+的标准规范,可以结合我的代码一起看。

    清晰理解细节之后,再动手写一遍。

    如果觉得不错的话,记得给我点赞 + star。

    撒花,感谢阅读!


    起源地下载网 » 前端进阶 - Promise原理&宏微任务

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元