资讯专栏INFORMATION COLUMN

Promise源码学习(1)

young.li / 3394人阅读

摘要:工作当中经常会用到,在此进行深入学习异步编程解决方案是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。所有源码注释见学习笔记

工作当中经常会用到Promise,在此进行深入学习

异步编程解决方案

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象.
关于其他异步方案,有许多精彩的文章,在此不再详述。接下来直接进入正题。
项目地址This is a polyfill of the ES6 Promise
项目结构

new Promise()
let p = new Promise(function (resolve, reject) {
  console.log(1);
  resolve(2);
})
p.then(function (val) {
  console.log(val);
})

这是一段简单实例,让我们跟着源码一起看发生了些什么,代码当中加入了个人理解的注释。
首先看一下整理后的promise.js

class Promise {
  constructor (resolver) {
    this[PROMISE_ID] = nextId(); //生成id
    this._result = this._state = undefined;
    this._subscribers = [];//订阅者

    //一般使用时,new时立即执行一次使用者传入的resolver,印证了一旦promise开始执行无法暂停
    if (noop !== resolver) {
      typeof resolver !== "function" && needsResolver();
      this instanceof Promise ? initializePromise(this, resolver) : needsNew();//调用resolver
    }
  }

  catch (onRejection) {
    return this.then(null, onRejection);
  }

  // finally 相当于对当前promise注册resolve和reject两种监听
  //如果为 resolve 执行一次cb 然后把原来的value继续传递
  finally (callback) {
    let promise = this;
    let constructor = promise.constructor;

    return promise.then(value => constructor.resolve(callback()).then(() => value),
      reason => constructor.resolve(callback()).then(() => {
        throw reason;
      }));
  }
}
Promise.prototype.then = then;
export default Promise;
Promise.all = all;
Promise.race = race;
Promise.resolve = Resolve;
Promise.reject = Reject;

细节均在注释当中。
这里主要是定义了Promise类和定义了一些方法如then,all等等,那么new Promise()主要是初始化了对象的一些属性,同时会立即执行resolver,印证了一旦promise开始执行无法暂停。接下来继续我们的思路,看initializePromise方法发生了什么。

//initializePromise(this, resolver)
function initializePromise (promise, resolver) {
  try {
    //执行resolver 传入回调
    resolver(function resolvePromise (value) {
      resolve(promise, value);
    }, function rejectPromise (reason) {
      reject(promise, reason);
    });
  } catch (e) {
    reject(promise, e);
  }
}

这里执行了我们传入的function,同时也给了使用者resolve和reject函数,由于reject相对简单,这里我们先看reject如何实现。

// 通用的reject方法
function reject (promise, reason) {
  if (promise._state !== PENDING) {
    return;
  }
  promise._state = REJECTED;
  promise._result = reason;

  asap(publishRejection, promise);//as soon as possible
}

reject方法中将promise的对象的状态设置为rejected,设置了执行的最终结果值result,随后再asap(调度执行策略)中执行publishRejection,通知通过then方法注册到_subscribers的订阅者们,我被reject啦!!,来执行回调,下面是publishRejection方法。

function publishRejection (promise) {
  if (promise._onerror) {
    promise._onerror(promise._result);
  }

  publish(promise);
}

//通用的publish
function publish (promise) {
  let subscribers = promise._subscribers;
  let settled = promise._state;

  //没有订阅者
  if (subscribers.length === 0) {
    return;
  }

  let child, callback, detail = promise._result;

  //这里i+=3 是因为then注册时 i是promise,i+1是resolve,i+2是reject
  for (let i = 0; i < subscribers.length; i += 3) {
    child = subscribers[i];
    callback = subscribers[i + settled];

    if (child) {
      invokeCallback(settled, child, callback, detail);//执行回调
    } else {
      callback(detail);
    }
  }
  //通知完毕,清除订阅
  promise._subscribers.length = 0;
}

上述就是reject之后的大致流程细节可以看注释。
让我们再回到这里:

//initializePromise(this, resolver)
function initializePromise (promise, resolver) {
  try {
    //执行resolver 传入回调
    resolver(function resolvePromise (value) {
      resolve(promise, value);
    }, function rejectPromise (reason) {
      reject(promise, reason);
    });
  } catch (e) {
    reject(promise, e);
  }
}

resolve发生了什么呢?

// 通用的resolve方法 继续传递执行
function resolve (promise, value) {
  if (promise === value) {//如果resolve原对象
    reject(promise, selfFulfillment());//设置rejected状态
  } else if (objectOrFunction(value)) {//如果val 是对象或函数
    handleMaybeThenable(promise, value, getThen(value));//getThen(value) 获取val.then方法
  } else {//not obj or not fnc
    fulfill(promise, value);//设置pending result val
  }
}

resolve一个值,这里分了三种情况处理
1、如果resolve原对象,直接reject,抛错。
2、如果是对象或函数,继续处理。
3、如果是简单值,//改变promise 状态为FULFILLED(完成状态) 同时设置result,发起publish

如果非要简单理解,resolve就是不断抽丝剥茧的处理直到给promise一个确定的完成态或拒绝态

fulfill方法比较简单,asap下文介绍

//改变promise 状态为FULFILLED(完成状态)  同时设置result
function fulfill (promise, value) {
  if (promise._state !== PENDING) {
    return;
  }

  promise._result = value;
  promise._state = FULFILLED;

  if (promise._subscribers.length !== 0) {//通知
    asap(publish, promise);
  }
}

这里重点看handleMaybeThenable方法

/*maybeThenable:value;then:value.then*/
function handleMaybeThenable (promise, maybeThenable, then) {//thenable obj or promise
  /* originalThen : 定义的原始then;originalResolve:原始resolve*/
  if (maybeThenable.constructor === promise.constructor && //判断是否是promise且没经修改原生方法
    then === originalThen && maybeThenable.constructor.resolve === originalResolve) {
    handleOwnThenable(promise, maybeThenable);//maybeThenable是一个原生的promise
  } else {//maybeThenable
    if (then === TRY_CATCH_ERROR) {// getThen 抛错
      reject(promise, TRY_CATCH_ERROR.error);
      TRY_CATCH_ERROR.error = null;//释放引用
    } else if (then === undefined) {//若不是一个thenable,直接完成态
      fulfill(promise, maybeThenable);
    } else if (isFunction(then)) {//若是一个thenable
      handleForeignThenable(promise, maybeThenable, then);
    } else {//若不是一个thenable,直接完成态
      fulfill(promise, maybeThenable);//改变promise 状态为完成态  同时设置result
    }
  }
}

这里就对传入的值做了详细区分
若是原生Promise对象:若是fulfilled或rejected,直接发起publish,如果是pending状态,调用then来注册订阅回调。
若是thenable:特殊处理handleForeignThenable
"其他fulfill或reject
这里看一下handleForeignThenable方法

/*
 * thenable 是函数,有then方法
 * */
//handleForeignThenable(promise, maybeThenable, then);
function handleForeignThenable (promise, thenable, then) {
  asap(promise => {//asap这里默认分析 setTimeout(fn,0) 下一轮任务开始时执行
    var sealed = false;//是否有结果
    //value:thenable传入的参数 ,尝试执行
    var error = tryThen(then, thenable, value => {//fullfill时,
      if (sealed) {
        return;
      }
      sealed = true;
      if (thenable !== value) {//如果不是直接resolve原对象
        resolve(promise, value);//继续对resolve的val进行resolve处理
      } else {
        fulfill(promise, value);
      }
    }, reason => {//reject
      if (sealed) {
        return;
      }
      sealed = true;

      reject(promise, reason);
    }, "Settle: " + (promise._label || " unknown promise"));

    if (!sealed && error) {//抛错 未正常执行resolve reject
      sealed = true;
      reject(promise, error);
    }
  }, promise);
}

看起来比较乱,但思路比较简单,这里就对thenable进行尝试执行,如果返回结果正常就继续resolve处理直到解析出一个值,否则抛错等等。

Then

上文说到了很多订阅啦,publish啦,订阅时哪里来的呢,上文只看到了每次执行完状态改变的时候要publish,publish给谁呢,then方法会给出答案

export default function then (onFulfillment, onRejection) {
  const parent = this;

  //新建一个不执行的promise对象用于返回结果,可链式调用
  const child = new this.constructor(noop);

  if (child[PROMISE_ID] === undefined) {//TODO
    makePromise(child);//初始化基本的promie 属性
  }
  //promise state
  const {_state} = parent;

  if (_state) {// 如果状态已完成或已拒绝,无需订阅,直接执行回调返回结果,印证了一旦promise有了结果无法再次改变
    const callback = arguments[_state - 1];
    asap(() => invokeCallback(_state, child, callback, parent._result));
  } else {//订阅来注册回调
    subscribe(parent, child, onFulfillment, onRejection);
  }
  //then reuturn的新promise
  return child;
}

这里情况分两种
已完成或已拒绝:直接执行回调返回结果,印证了一旦promise有了结果无法再次改变
pending未完成:订阅来注册回调
这里先看invokeCallback方法。

//asap(() => invokeCallback(_state, child, callback, parent._result));
//执行回调:
function invokeCallback (settled, promise, callback, detail) {
  let hasCallback = isFunction(callback),
    value, error, succeeded, failed;

  if (hasCallback) {
    value = tryCatch(callback, detail);//尝试执行使用者then()传入的回调,成功时value 是then()注册的回调方法的返回值

    if (value === TRY_CATCH_ERROR) {
      failed = true;
      error = value.error;
      value.error = null;
    } else {
      succeeded = true;
    }

    if (promise === value) {//若return this
      reject(promise, cannotReturnOwn());
      return;
    }

  } else {// then 未传入相关回调,继续传递
    value = detail;
    succeeded = true;
  }

  if (promise._state !== PENDING) {
    // noop
  } else if (hasCallback && succeeded) {
    resolve(promise, value);//value 可能为thenable,继续处理,抽丝剥茧
  } else if (failed) {//有cb 且失败
    reject(promise, error);
  } else if (settled === FULFILLED) {//无cb
    fulfill(promise, value);
  } else if (settled === REJECTED) {//无cb
    reject(promise, value);
  }
}

接下来是重要的subscribe方法。

/*
 * parent:thenable
 * child : undefined or other
 * */
//subscribe(parent, child, onFulfillment, onRejection);
//如果promise仍是pending,则将回调函数加入_subscribers等待通知
function subscribe (parent, child, onFulfillment, onRejection) {
  let {_subscribers} = parent;//取注册的所有订阅
  let {length} = _subscribers;

  parent._onerror = null;

  _subscribers[length] = child;//扩充订阅  3个一循环
  _subscribers[length + FULFILLED] = onFulfillment;
  _subscribers[length + REJECTED] = onRejection;

  /*
  * 1、如果之前有订阅且状态是pending, 订阅就好了,等待resolve完成时的发布通知执行就好
  * 2、如果之前有订阅且状态不是pending,继续加入订阅就好,length=0时已经准备调度发布了,pulish执行时会清空
  * 3、如果之前无订阅且状态是pending,订阅就好了,等待resolve完成时的发布通知执行就好
  * 4、如下,赶紧调度执行获取结果
  * */
  if (length === 0 && parent._state) {//如果之前没有订阅且thenable已不是pending,
    asap(publish, parent);
  }
}

上面就是订阅的过程,主要是利用的js单线程的特性,且需要和fuifill和reject执行时发布publish一起理解.
下面是asap方法

//下一轮事件循环执行
export var asap = function asap (callback, arg) {
  queue[len] = callback;//2个一组
  queue[len + 1] = arg;
  len += 2;
  if (len === 2) {
    /*
     如果队列长度是2 ,那意味着我们需要调度一次队列flush,
     如果队列flush完成前有其他的回调进入队列,这些进入的回调会在当前已调度的flush执行
     * */

    // If len is 2, that means that we need to schedule(调度) an async flush.
    // If additional callbacks are queued before the queue is flushed, they
    // will be processed by this flush that we are scheduling.
    if (customSchedulerFn) {
      customSchedulerFn(flush);
    } else {//一般默认
      scheduleFlush();
    }
  }
}

function flush () {
  for (let i = 0; i < len; i += 2) {
    let callback = queue[i];
    let arg = queue[i + 1];

    callback(arg);

    queue[i] = undefined;
    queue[i + 1] = undefined;
  }

  len = 0;//逻辑清空队列
}

本文默认分析采用的调度策略时setTimeout方法,asap里维护了一个执行队列queue。这里涉及到了一些
js异步编程机制,推荐阅读从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

结语

本文主要是跟着源码的思路简单过了一遍代码,加入了个人的理解。同时还有一些如Promise.all等等方法将在下篇一起分析。
阅读代码前,也学习了阮一峰老师关于Promise的文章,在此一并感谢。
所有源码注释见promise学习笔记

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94256.html

相关文章

  • Promise源码学习(2)

    摘要:源码学习本篇为上一篇源码学习的补充,主要是来介绍和方法。那个率先改变的实例的返回值,就传递给的回调函数。基本介绍可见阮一峰老师的书籍。的状态由决定,分成两种情况。只有的状态都变成,的状态才会变成,此时的返回值组成一个数组,传递给的回调函数。 Promise源码学习(2) 本篇为上一篇源码学习(1)的补充,主要是来介绍Promise.all()和Promise.race()方法。闲话少叙...

    cfanr 评论0 收藏0
  • Promise学习笔记(四):源码core.js解析(下)

    摘要:源码阅读阶段紧接上一篇这次我们开始我们最常用到的部分的源码解析传入参数为两个函数和判断调用者是否为对象跳转到了一个叫做的函数里面新建一个对象传入函数传入给和一个新的对象返回新的对象在这里我们先看看在调用者不是对象时到底做了什么比想象的要简单 源码阅读阶段 紧接上一篇,这次我们开始Promise我们最常用到的then部分的源码解析. then() //传入参数为两个函数,onFulfil...

    VincentFF 评论0 收藏0
  • Promise学习笔记(三):源码core.js解析(上)

    摘要:源码阅读阶段先理解根本吧想快点理解的话可以直接跳到下个标题这部分根据理解将持续修改空函数用于判断传入构造器的函数是否为空函数如果为空函数构造一个对象并初始化状态为终值回调状态和队列记录内部最后的一次错误空对象标识表示发生了错误暴露模块接口为 源码阅读阶段 先理解Promise根本吧,想快点理解的话可以直接跳到下个标题.这部分根据理解将持续修改. Promise(fn) function...

    wuyangchun 评论0 收藏0
  • 从react-start到co源码(三)

    摘要:第三篇脚手架依赖的核心库的源码解析。该篇是这个系列文章的第三篇主要是对的源码进行分析讲解。的源码十分简单但实现的功能却是十分的强大。源码概括源码主要包含了两部分公共方法和私有方法。 react作为当前十分流行的前端框架,相信很多前端er都有蠢蠢欲动的学习它的想法。工欲善其事,必先利其器。这篇文章就简单的给大家介绍一下如何我快速的搭建一个react前端开发环境。主要针对于react小白,...

    wind5o 评论0 收藏0
  • Promise学习笔记(二):规范

    摘要:下一篇大概就是源码方面的学习笔记了龟速学习中这一次我是去看了下规范照例传送门图灵社区规范首先吧个人总结下该用的词解决结婚拒绝婉拒终值值传家宝拒因好人卡等等异常车祸理下概念我们的的就像是一场姻缘对吧解决呢就是结婚成功啦传家宝也如愿的传给下一代 下一篇大概就是源码方面的学习笔记了...龟速学习中... 这一次我是去看了下Promises/A+规范照例传送门:图灵社区Promises/A+规...

    _Suqin 评论0 收藏0

发表评论

0条评论

young.li

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<