promise.then异步实现方法对比(简易手写vs源码解析)

手写:setTimeout

我们手写一个promise/A+规范的Promise时,一般在实现then方法时会采用setTimeout来实现异步操作:

then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v
        onRejected = typeof onRejected === "function" ? onRejected : (v) => {throw v}
        if (this.state === "fulfilled") {
            setTimeout(() => {
                onFulfilled(this.result)
            })
        } else if (this.state === "rejected") {
            setTimeout(() => {
                onRejected(this.result) 
            })
        }else if (this.state === "pending") {
            this.#handlers.push({
                onFulfilled: () => {
                    setTimeout(() => {
                        onFulfilled(this.result)
                })}, 
                onRejected: () => {
                    setTimeout(() => {
                        onRejected(this.result)
                })}
            })
        }
    }

源码:使用微任务

源码地址:https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.promise.constructor.js
then方法实现路径文件:core-js/packages/core-js/modules/es.promise.constructor.js

与使用 setTimeout 相比,微任务的执行时机更早,因此能够更有效地管理异步操作。

1. 定义 Promise 原型对象的 then 方法

reaction 对象:

  • reaction 对象用于封装 then 方法中传入的成功和失败回调函数,以及与之关联的新 Promise 对象。
  • 它包含了以下属性:
    • ok:用于存储成功回调函数,如果传入的回调函数是可调用的,则将其存储在这里。
    • fail:用于存储失败回调函数,如果传入的回调函数是可调用的,则将其存储在这里。
    • promise:用于保存 then 方法返回的新 Promise 对象。
    • domain:用于存储当前 Promise 对象所属的域(如果支持)。

可以看到,在下述代码中,使用了 microtask微任务(在下面具体解析相关代码) 处理异步操作

var microtask = require('../internals/microtask');

// `Promise.prototype.then` method
  Internal.prototype = defineBuiltIn(PromisePrototype, 'then', function then(onFulfilled, onRejected) {
    // 获取了当前 Promise 对象的内部状态对象 state,它用于跟踪 Promise 对象的状态、值和回调函数等信息。
    var state = getInternalPromiseState(this);    
    
    // newPromiseCapability 函数会创建一个新的 Promise 对象,将其赋值给 promise 属性
    var reaction = newPromiseCapability(speciesConstructor(this, PromiseConstructor));
    state.parent = true;
    
    // 如果 onFulfilled 是一个可调用的函数,则将其赋值给 ok 属性,否则将 true 赋值给 ok,表示不需要执行成功回调函数。
    reaction.ok = isCallable(onFulfilled) ? onFulfilled : true;
    // 如果 onRejected 是一个可调用的函数,则将其赋值给 fail 属性,否则将 undefined 赋值给 fail,表示不需要执行失败回调函数。
    reaction.fail = isCallable(onRejected) && onRejected;
    reaction.domain = IS_NODE ? process.domain : undefined;
    
    // 如果是PENDING状态,则将 reaction 对象添加到当前 Promise 对象的 reactions 队列中。
    if (state.state === PENDING) state.reactions.add(reaction);
    // 否则 通过微任务来异步执行 callReaction 函数,以处理 reaction 对象。
    else microtask(function () {
      callReaction(reaction, state);
    });
    return reaction.promise;
  });
2. 处理 reaction 对象的核心函数

(重点关注注释部分即可)

var callReaction = function (reaction, state) {
  var value = state.value;
  var ok = state.state === FULFILLED;
  var handler = ok ? reaction.ok : reaction.fail;
  var resolve = reaction.resolve;
  var reject = reaction.reject;
  var domain = reaction.domain;
  var result, then, exited;
  
  try {
    // 根据当前 Promise 的状态来选择应该执行的处理函数
    if (handler) {
      // 处理当前 Promise 的状态不是 FULFILLED 的情况
      if (!ok) {
        if (state.rejection === UNHANDLED) onHandleUnhandled(state);
        state.rejection = HANDLED;
      }
      // 处理特殊情况,即处理函数为 true 的情况,将当前 Promise 的值直接赋给 result
      if (handler === true) result = value;
      else {
        …………(一些domain的代码,省略)
      }
      
      // 如果 result 等于 reaction.promise,则说明存在 Promise 链循环,需要拒绝当前 Promise
      if (result === reaction.promise) {
        reject(new TypeError('Promise-chain cycle'));
        // 如果 result 是一个 Promise 对象,则执行链式调用
      } else if (then = isThenable(result)) {
        call(then, result, resolve, reject);
        // 否则 resolve 当前 Promise
      } else resolve(result);
    } else reject(value);
  } catch (error) {
    …………
    reject(error);
  }
};

result === reaction.promise为什么reject?

这个情况表明成功或失败处理函数返回的结果正好是当前 then 方法返回的新 Promise 对象 reaction.promise。
这种情况下,就会判断存在链循环,因为在 Promise 链中,每个 then 方法返回的新 Promise 对象应该是一个新的实例,而不应该与前一个 then 方法返回的 Promise 对象相同。

3. microtask文件实现

(重点关注注释部分)

'use strict';
var global = require('../internals/global');
var safeGetBuiltIn = require('../internals/safe-get-built-in');
var bind = require('../internals/function-bind-context');
var macrotask = require('../internals/task').set;
var Queue = require('../internals/queue');
var IS_IOS = require('../internals/engine-is-ios');
var IS_IOS_PEBBLE = require('../internals/engine-is-ios-pebble');
var IS_WEBOS_WEBKIT = require('../internals/engine-is-webos-webkit');
var IS_NODE = require('../internals/engine-is-node');
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver;
var document = global.document;
var process = global.process;
var Promise = global.Promise;

// 微任务方法获取,现代引擎提供的 queueMicrotask 方法,如果支持直接使用
var microtask = safeGetBuiltIn('queueMicrotask');
var notify, toggle, node, promise, then;

// 不支持queueMicrotask的实现:
if (!microtask) {
  var queue = new Queue();

  // 执行微任务队列中的任务
  var flush = function () {
    var parent, fn;
    if (IS_NODE && (parent = process.domain)) parent.exit();
    // 遍历微任务队列,队列为空循环结束
    while (fn = queue.get()) try {
      fn();
    } catch (error) {
      if (queue.head) notify();
      throw error;
    }
    if (parent) parent.enter();
  };

  // 根据当前环境的特性来选择最适合的微任务实现方式
  
  //不在 iOS、Node.js 和 WebOS Webkit 环境中,同时 MutationObserver 和 document 存在 ,使用MutationObserver监听
  if (!IS_IOS && !IS_NODE && !IS_WEBOS_WEBKIT && MutationObserver && document) {
    toggle = true;
    node = document.createTextNode('');
    new MutationObserver(flush).observe(node, { characterData: true });
    notify = function () {
      node.data = toggle = !toggle;
    };
  // 不在 iOS Pebble 环境下,且Promise和Promise.resolve存在,创建一个立即resolved的 Promise 对象,并通过 then 方法来执行微任务队列中的任务。
  } else if (!IS_IOS_PEBBLE && Promise && Promise.resolve) {
    promise = Promise.resolve(undefined);
    promise.constructor = Promise;
    then = bind(promise.then, promise);
    notify = function () {
      then(flush);
    };
  // 如果在 Node.js 环境,使用 process.nextTick 方法来执行微任务队列中的任务。
  } else if (IS_NODE) {
    notify = function () {
      process.nextTick(flush);
    };
  // 以上所有条件都不满足,那么代码将使用宏任务(macrotask)
  // 比如 setImmediate、MessageChannel、window.postMessage、onreadystatechange 或 setTimeout。
  } else {
    // `webpack` dev server bug on IE global methods - use bind(fn, global)
    macrotask = bind(macrotask, global);
    notify = function () {
      macrotask(flush);
    };
  }

 
  microtask = function (fn) {
    if (!queue.head) notify();
    queue.add(fn);
  };
}

module.exports = microtask;
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值