博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Promise源码学习(1)
阅读量:7168 次
发布时间:2019-06-29

本文共 9661 字,大约阅读时间需要 32 分钟。

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

异步编程解决方案

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,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);}//通用的publishfunction 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(完成状态)  同时设置resultfunction 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异步编程机制,推荐阅读

结语

本文主要是跟着源码的思路简单过了一遍代码,加入了个人的理解。同时还有一些如Promise.all等等方法将在下篇一起分析。

阅读代码前,也学习了阮一峰老师关于Promise的文章,在此一并感谢。
所有源码注释见

转载地址:http://ixtwm.baihongyu.com/

你可能感兴趣的文章
swift百度地图api
查看>>
sql server 排序规则
查看>>
CCNA2.0笔记_OSPF v3
查看>>
测试工程师应该知道的数据库基本操作(增删改查)
查看>>
求二叉树中的节点个数、求二叉树的深度(高度)
查看>>
看到一些前端面试题没答案,自己做了一下如果有错请指出
查看>>
Selenium2(WebDriver)总结(四)---基本元素操作
查看>>
应用程序框架实战三十五:服务概述
查看>>
C#中一道关于多线程的编程题
查看>>
附加数据库 对于 服务器“00-PC”失败
查看>>
AgileEAS.NET SOA中间件平台更新日志 2015-04-28
查看>>
系统共享内存的修改(ORA-27102: out of memory)
查看>>
设备树(device tree)学习笔记
查看>>
hdu 3518 (后缀数组)
查看>>
C#类的一些概念
查看>>
bzoj 4001 [TJOI2015]概率论 数学
查看>>
基于.net开发chrome核心浏览器【二】
查看>>
LeetCode - Isomorphic Strings
查看>>
android UI 仿 win 8 模块化 标题,并实现 可长按拖动交换图片位置、可点击,且伴随动画特效...
查看>>
Maven发布工程到公共库
查看>>