在js中深入分析promise的内部实现原理及常见问题

本文详细解析Promise的内部实现原理,包括其在异步编程中的角色、状态管理、构造函数及then方法的工作机制。同时探讨Promise与thenable对象的关系,以及如何处理异步执行中的错误和异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Promise的内部实现原理

  1. Promise
    1)Promise 是异步编程的一种解决方案,比传统的异步解决方案回调函数和事件更合理、更强大,现已被 ES6 纳入进规范中,Javascript 异步操作的基本单位也逐渐从 callback 转换到 promise
    2)Promise 表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符
    3)Promise会做出正确延时或异步操作,会解决callback处理异步回调可能产生的调用过早,调用过晚、调用次数过多过少、吞掉可能出现的错误或异常问题等,只接受首次 resolve(…)或 reject(…) 决议,本身状态转变后不会再变,所有通过 then(…) 注册的回调总是依次异步调用,所有异常总会被捕获抛出
    4)严谨来讲,Promise 是一种封装和组合未来值得易于复用机制,实现关注点分离、异步流程控制、异常冒泡、串行/并行控制等

  2. thenable 对象
    1)thenable 是一个定义 then(…) 方法的对象或函数
    2)thenable 对象的存在目的是使 Promise 的实现更具有通用性,只要其暴露出一个遵循 Promise/A+ 规范的 then(…) 方法,同时也会使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存
    3)类型检查:也可以称之为鸭式辩型(duck typing),识别 thenable 或行为类似 Promise 对象可以根据其是否具有 then(…) 方法来判断

  3. then 回调异步执行
    1)Promise 实例化时传入的函数会立即执行,then(…) 中的回调需要异步延迟调用
    2)回调函数异步调用时机:可以理解为onFulfilled 或 onRejected 只在执行环境堆栈仅包含平台代码时才可被调用
    3)实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行,这个事件队列可以采用宏任务 macro-task机制或微任务 micro-task机制来实现
    4)在ES6中,Promise 必须以 Promise Job 形式加入 job queues,也就是微任务micro-task。Job Queue 是 ES6 中新提出的概念,建立在事件循环队列之上,job queue存在也是为了满足一些低延迟的异步操作
    5) macrotask和 microtask 分别表示异步任务的两种分类,在挂起任务时,JS 引擎会将所有任务按照类别分到两个队列中,首先在 macrotask 的队列(也叫 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完

  4. Promise 状态
    1)Promise 的三种状态:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise被resolve或reject,不能再迁移至其他任何状态(即状态 immutable)
    2)fulfilled 使用 resolved 代替,onFulfilled 使用 onResolved 代替

  5. Promise 构造函数
    1)Promise构造函数需要完成
    初始化 Promise 状态(pending)
    初始化 then(…) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
    立即执行传入的 fn 函数,传入Promise 内部 resolve、reject 函数
    2)then 方法可以被同一个 promise 调用多次
    3)如果需要多次调用 then 注册回调处理,内部选择使用 _deferreds 数组存储处理对象

6.then 函数
1)then 方法可以被同一个 promise 调用多次,每次返回新 promise 对象
2)then 方法接受两个参数onResolved、onRejected(可选),在 promise 被 resolve 或 reject 后,所有 onResolved 或 onRejected 函数须按照其注册顺序依次回调,且调用次数不超过一次
3)then 函数执行流程
实例化空 promise 对象用来返回(保持then链式调用)
构造 then(…) 注册回调处理函数结构体
判断当前 promise 状态,pending 状态存储延迟处理对象 deferred ,非pending状态执行 onResolved 或 onRejected 回调
4)Handler 函数封装存储 onResolved、onRejected 函数和新生成 promise 对象

  1. 链式调用返回新的promise
    1)为保证 then 函数链式调用,then 需要返回 promise 实例
    2) Promise 状态一旦 resolved 或 rejected就不能再迁移
    3)handleResolved 函数功能为根据当前 promise 状态,异步执行 onResolved 或 onRejected 回调函数,因在 resolve 或 reject 函数内部同样需要相关功能,提取为单独模块

  2. resolve 函数
    1)Promise 实例化时立即执行传入的 fn 函数,同时传递内部 resolve 函数作为参数用来改变 promise 状态
    2)resolve 函数:判断并改变当前 promise 状态,存储 resolve(…) 的 value 值,判断当前是否存在 then(…) 注册回调执行函数,若存在则依次异步执行 onResolved 回调
    3)为使 Promise 的实现更具有通用性,当 value 为存在 then(…) 方法的 thenable 对象,需要做 Promise Resolution Procedure 处理,规范描述为 [[Resolve]](promise, x),x 即 为后面 value 参数
    4)处理逻辑流程
    如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    如果 x 为 Promise ,则使 promise 接受 x 的状态
    如果 x 为对象或函数
    把 x.then 赋值给 then
    如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    如果 then 是函数,将 x 作为函数的作用域 this 调用之
    如果 x 不为对象或者函数,以 x 为参数执行 promise
    5)resolve 函数逻辑较为复杂,主要集中在处理 value(x)值多种可能性
    6)如果 value 为 Promise 且状态为pending时,须使 promise 接受 value 的状态
    在 value 状态为 pending 时,简单将 promise 的 deferreds 回调处理数组赋予 value deferreds变量
    非 pending 状态,使用 value 内部值回调 promise 注册的 deferreds
    如果 value 为 thenable 对象,以 value 作为函数的作用域 this 调用之,同时回调调用内部 resolve(…)、reject(…)函数
    其他情形则以 value 为参数执行 promise,调用 onResolved 或 onRejected 处理函数
    7)Promise Resolution Procedure 处理流程是用来处理 then(…) 注册的 onResolved 或 onRejected 调用返回值 与 then 新生成 promise 之间关系,不过考虑到 fn 函数内部调用 resolve(…)产生值 与当前 promise 值仍然存在相同关系,逻辑一致,写进相同模块

  3. reject 函数
    1)Promise 内部私有方法 reject 相较于 resolve 逻辑简单很多
    2)resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
    reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去

  4. handleResolved 函数
    1)handleResolved 函数具体会根据 promise 当前状态判断调用 onResolved、onRejected,处理 then(…) 注册回调为空情形,以及维护链式 then(…) 函数后续调用
    2)使用 deferred.promise作为当前 promise 结合 value 调用后续处理函数继续往后执行,实现值穿透空处理函数往后传递
    3)实现 then 函数的链式调用,只需要在Promise.prototype.then(..)处理函数中返回新的 promise 实例即可,还需要依次调用 then 注册的回调处理函数

  5. then 注册回调函数异步执行
    1)为屏蔽依赖外部的不确定性,规范指定 onFulfilled 和 onRejected 方法异步执行

12.promise 内部错误或异常
1)如果 promise 被 rejected,则会调用拒绝回调并传入拒由,比如在 Promise 的创建过程中(fn执行时)出现异常,那这个异常会被捕捉并调用 onRejected
2)如果 Promise 完成后调用 onResolved 查看结果时出现异常错误,此时 onRejected 不会被触发执行,因为 onResolved 内部异常并不会改变当前 promise 状态(仍为resolved),而是改变 then 中返回新的 promise 状态为 rejected,异常未丢失但也未调用错误处理函数
3)Promise.prototype.catch方法,假如你对 onResolved 处理过程没有信心或存在异常 case 情况,最好还是在 then 函数后调用 catch 方法做异常捕获兜底处理

二、Promise的常见问题

  1. 创建promise的流程
    1)使用new Promise(fn)或者它的快捷方式Promise.resolve()Promise.reject(),返回一个promise对象
    2)在fn中指定异步的处理
    处理结果正常,调用resolve
    处理结果错误,调用reject

  2. reject 和 catch 的区别
    1)promise.then(onFulfilled, onRejected)在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的
    2)promise.then(onFulfilled).catch(onRejected) .then中产生的异常能在.catch中捕获
    3)建议使用第二种,因为能捕获之前的所有异常
    4)第二种的.catch()也可以使用.then()表示,它们本质上是没有区别的,.catch === .then(null, onRejected)

  3. 如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误

  4. 在异步回调中抛错,不会被catch到

  5. promise状态变为resove或reject,就凝固了,不会再改变

三、Promise的综合理解

  1. Promise的实例实现
    1)需求分析:
    Promise可以理解为保姆,所有的事情都可以吩咐它去做,你就可以做其它的事情了
    场景: 比如一天我需要加班,老婆也要加班,需要去送饭,所以我让保姆去做这个事情
    保姆需要完成的任务是:
    先去超市买菜,买菜的任务
    然后用超市买回来的菜做饭,做饭的任务
    将做好的菜送到老婆的单位,送饭的任务
    送完饭后打电话和我通知说一下,打电话通知的任务
    第一、二、三个任务为异步任务,第四个任务为同步任务
    2)实例代码:
     //  买菜的任务
    function buyVegetables(){
        var p1 = new Promise(function(resolve,reject){
            setTimeout(function(){
                resolve(["鸡蛋","西红柿","辣椒"]);
            },3000);
        });
        return p1;
    }

    //  做饭的任务
    function cookDinner(){
        var p2 = new Promise(function(resolve,reject){
            setTimeout(function(){
                resolve({
                    food:"米饭",
                    greens:"西红柿炒蛋"
                });
            },3000);
        });
        return p2;
    }

    // 送饭的任务
    function mealDelivery(){
        var p3 = new Promise(function(resolve,reject){
            setTimeout(function(){
                resolve("饭做好了,开始去送饭了");
            },3000);
        });
        return p3;
    }

    // 打电话通知的任务
    function inform(){
      var p4 = new Promise(function(resolve,reject){
          console.log("饭已经送到了,可以吃饭了");
      });
      return p4;
    }


    //  告诉保姆任务的执行
    /*
    *  输出:
    *       (3) ["鸡蛋", "西红柿", "辣椒"]
    *       {food: "米饭", greens: "西红柿炒蛋"}
    *       饭做好了,开始去送饭了
    *       饭已经送到了,可以吃饭了
    */
    //  第一个、第二个和第三个是异步任务,每隔三秒后才出现,第四个是同步任务,与第三个一起出现
    //  一个 Promise 对象有三个状态,并且状态一旦改变,便不能再被更改为其他状态
    //   pending,异步任务正在进行    resolved (也可以叫fulfilled),异步任务执行成功    rejected,异步任务执行失败
    buyVegetables().then(function(data){
        console.log(data);
        return cookDinner();
    }).then(function(data){
        console.log(data);
        return mealDelivery();
    }).then(function(data){
       console.log(data);
        return inform();
    });
  1. Promise的总结
    1)首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象
    new Promise(fn)Promise.resolve(fn)
    2)然后调用上一步返回的 promise 对象的 then 方法,注册回调函数,then 中的回调函数可以有一个参数,也可以不带参数,如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数
    3)最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常

  2. Promise的事件循环
    1)Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环

  3. Promise的升级
    1)ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致
    2)async/await也是基于 Promise 实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值