资讯专栏INFORMATION COLUMN

Promise学习笔记(三):源码core.js解析(上)

wuyangchun / 2572人阅读

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

源码阅读阶段

先理解Promise根本吧,想快点理解的话可以直接跳到下个标题.这部分根据理解将持续修改.

Promise(fn)
function noop() {}
/*
空函数,用于判断传入Promise构造器的函数是否为空函数,如果为空函数构造一个promise对象并初始化状态为pending,终值null,回调状态0和队列null.
*/
var LAST_ERROR = null;//记录Promise内部最后的一次错误
var IS_ERROR = {}; //空对象,标识表示发生了错误
module.exports = Promise; //暴露模块接口为Promise
function Promise(fn) {
  if (typeof this !== "object") {
    throw new TypeError("Promises must be constructed via new");
  }
  if (typeof fn !== "function") {
    throw new TypeError("Promise constructor"s argument is not a function");
  }
  this._deferredState = 0;
  this._state = 0; //promise状态
  this._value = null; //resolve返回的结果
  this._deferreds = null;
  if (fn === noop) return;
  doResolve(fn, this); //处理函数
}
Promise._onHandle = null;
Promise._onReject = null;
Promise._noop = noop;

原文中表示将带有_前缀的变量在构造的时候转为(_随机数)的形式,来混淆和阻止它们被使用.接下来列出说明重要的变量.

* _defferedState = 0
表示_deferreds的类型,0时表示null,1时表示单个handler(后面讲述),2表示多个deferreds(数组)
* _state = 0
promise状态,0为pending,1为fulfilled,2为rejected,3则为值已被传递给下个promise.
* _value = null
resolve返回的结果,也就是我们所说的终值/拒因
* _deferreds = null
表示单个或多个handler(后面讲述)
doResolve(fn,this)

相比大家都看到这行函数了doResolve(fn, this);,这里也就是我们初始化一个Promise时会做的事了,我们在看这个函数前,先理解下源码中类似于工具函数一样的函数.

//获取then方法,没有then方法标识错误
function getThen(obj) {
  try {
    return obj.then;
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}
//内部捕获错误,单个参数函数
function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}
//内部捕获错误,两个参数函数
function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

接下来我们直接跳到doResolve(fn,promise)

function doResolve(fn, promise) {//传入参数为一个函数,一个promise对象
  var done = false; //保证了规范中提到的一次性
  var res = tryCallTwo(fn, function (value) {//看到了么,用于捕获错误.
    if (done) return; //这里不用说了,为了保证两个函数中只有一个函数运行且仅运行一次
    done = true;
    resolve(promise, value);//后续再分析它
  }, function (reason) {
    if (done) return;
    done = true;
    reject(promise, reason);//后续再分析它
  });
  if (!done && res === IS_ERROR) {
    done = true;
    reject(promise, LAST_ERROR);//后续再分析它
  }
}

这就是我们的doResolve函数,可以看出,它只是个中间件,用于干什么的呢,就是解决传入函数error问题并进行reject的.重点是调用了我们很眼熟的两个函数,resolve()reject()

resolve() and reject()

在这个函数里我们找到了两个新东西,resolve()reject(),看名字就知道这两个函数是什么啦,我们先看reject()

function reject(self, newValue) {//两个参数,从doResolve我们可以知道self是一个promise对象,而newValue就是拒因啦
  self._state = 2;//状态变成rejected了
  self._value = newValue;//promise中结果变为拒因
  if (Promise._onReject) {//在core.js中它为null,所以可能是用于别的功能.我们直接跳过
    Promise._onReject(self, newValue);
  }
  finale(self);//新的函数又出现了.
}

reject()函数传入了promise对象和一个reason拒因,函数做的就是将promise的状态变为rejected,并将promise的值进行更改.然后调用finale()函数

可以看到出现了新函数finale(),并且传了已经进行完reject的promise对象给它.但是我们还是先看resolve()

function resolve(self, newValue) {
//这里写的其实就是按照规范处理的流程
  /* Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure */
  if (newValue === self) {//传入值等于自己就抛出错误
    return reject(
      self,
      new TypeError("A promise cannot be resolved with itself.")
    );
  }
  if (//值为对象或函数
    newValue &&
    (typeof newValue === "object" || typeof newValue === "function")
  ) {
    var then = getThen(newValue);//获取值中的then函数
    if (then === IS_ERROR) {//不存在then,reject去
      return reject(self, LAST_ERROR);
    }
    if (//存在并且原来值它是一个promise对象
      then === self.then &&
      newValue instanceof Promise
    ) {//同步两个promise,将传入的promise状态变为已传递并把newValue这个promise对象作为promise的值,然后finale并退出函数.
      self._state = 3;
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === "function") {//如果获取到then的值不为promise,但then是一个函数(thenable)
      doResolve(then.bind(newValue), self);//这里可以看看上个博文,对这个情况有说明,对终值自身进行doResolve取得新的值作为新的终值.
      return;
    }
  }
  self._state = 1;//promise状态为fulfilled
  self._value = newValue;//值传递
  finale(self);//finale了
}

resolve()中,我们照样是传进了一个promise对象和value(终值),函数内部通过标准的判断(详细参考学习笔记(二):规范),进行[[Resolve]]操作,最后将promise对象状态变更为fulfilled并改变其终值,调用finale.

finale()

我们可以进行finale()的分析了,毕竟我们的核心函数都指向它呢.

function finale(self) {//传入了一个promise对象
  if (self._deferredState === 1) {//判断deferreds是单个
    handle(self, self._deferreds);//传入了promise对象和promise对象中的_deferreds
    self._deferreds = null;//让deferreds为null
  }
  if (self._deferredState === 2) {//判断deferreds是数组
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);//传入了promise对象和promise对象中的_deferreds数组的所有数据
    }
    self._deferreds = null;//让deferreds为null
  }
}

很好,这下都是新的东西了,_deferredState这个就是判断_deferreds是数组还是单个的情况,并对其中每一个deferred进行handle调用.但是_defferreds又是什么呢,handle()这个函数又做了什么处理呢...

handle()
function handle(self, deferred) {//这个传入参数是预想之中的
  while (self._state === 3) {//promise状态为3的时候,也就是该promise已传递完毕的时候
    self = self._value;//重定位self为promise传递的值
  }
  if (Promise._onHandle) {//同样不属于本篇考虑范畴
    Promise._onHandle(self);
  }
  if (self._state === 0) {//promise状态为pending时
    if (self._deferredState === 0) {//没有deferreds时
      self._deferredState = 1;//deferreds变为单个
      self._deferreds = deferred;传入deferreds入列
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;//deferreds变为数组
      self._deferreds = [self._deferreds, deferred];//传入deferred进入数组
      return;
    }
    self._deferreds.push(deferred);//已经是数组了就直接push增加
    return;
  }
  handleResolved(self, deferred);//新东西,在state!==0时传入promise和defered
}

可以看到其实这个函数在对_deferreds进行添加,进行着_deferreds的修改和写入,与finale()所做的事情恰恰相反,但是详细的处理却还是在handleResolved()函数里面.

handleResolved()
function handleResolved(self, deferred) {
  asap(function() {//注意!asap是一个引入的模块,意思就是as soon as possible,就是尽快执行的意思,我们不需要考虑它做了什么.
    // promise状态是fulfilled情况下cb为deferred中的Fulfilled函数,不是的话则为onRejected函数...
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {//如果不存在对应状态的函数
      if (self._state === 1) {//当前promise对象是否为fulfilled状态
        resolve(deferred.promise, self._value);//传入deferred中保存的promise和当前promise的值进行resolve
      } else {
        reject(deferred.promise, self._value);//与上类似,进行reject
      }
      return;
    }
    var ret = tryCallOne(cb, self._value);//存在则尝试执行对应函数,返回执行结果(与两个参数的tryCall不同,这里返回了函数运行结果)
    if (ret === IS_ERROR) {//有错误,reject
      reject(deferred.promise, LAST_ERROR);
    } else {//没错误,对deferred.promise用函数返回值进行resolve
      resolve(deferred.promise, ret);
    }
  });
}

这里我们看到了deferred是一个保存了promise对象,onFulfilled函数,onRejected函数的对象,相当于一个保存现场.其实这里就是我们即将在源码core.js解析(下)写到的handler对象了.但是这里我们暂且先不深究,知道就好了.
handleResolved()毫无疑问就是在进行着promise的终值传递处理,对旧promise对象的状态修改,并调用resolvereject获取到值/拒因向下一个Promise传递.对这里的详细实例分析我们放到(下)来讲.

构造一个Promise时发生了啥?

从简单看起,我们构造一个Promise对象的时候经过了哪些函数
先理一下思路.

var A = Promise(function(resolve,reject){
    resolve("ok");
})

这里面首先是先构造了Promise对象,我们称为A,在构造阶段执行了如下代码.
检查略....

A._deferredState = 0;
A._state = 0;
A._value = null;
A._deferreds = null;

检查传入参数不为空....

doResolve(function(resolve,reject){
    resolve("ok");
},this);

然后我们跳到了doResolve()函数处,传入为fn,promise

res = tryCallTwo(fn,function(value){resolve(promise, value);},function(reason){reject(promise, reason);});
出错就reject(promise,LAST_ERROR)

我们又根据我们的输入跳转到了resolve(promise,value)啦,这里理一下我们的函数,promise就是A,value其实就是我们传入的"ok".
所以执行的是promise内部中的resolve(promise,"ok")
经过一系列判断(详细可以看规范),我们的"ok"过五关斩六将直接执行到这一步.

self._state = 1;//A状态变为fulfilled
self._value = newValue;//AA终值变为"ok"

然后我们finale(self)啦,继续跳到finale()函数,传入了A.

//在过程中我们的_deferredState始终为0呀,看了一下
A._deferredState = 0;
//已经没有什么好做的了...

我们的构造已经结束了!这就是我们new 一个Promise时的过程,跟着思路走,其实加上reject也是同样的路线而已.

(上)结语

(下)的话我打算重点写剩余部分的then函数流程,并尽量用简单的语言来描述,希望自己能进一步理解promise,发现有错漏能及时修订.

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

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

相关文章

  • Promise学习笔记(四):源码core.js解析(下)

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

    VincentFF 评论0 收藏0
  • JS笔记

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

    rottengeek 评论0 收藏0
  • 前端最实用书签(持续更新)

    摘要:前言一直混迹社区突然发现自己收藏了不少好文但是管理起来有点混乱所以将前端主流技术做了一个书签整理不求最多最全但求最实用。 前言 一直混迹社区,突然发现自己收藏了不少好文但是管理起来有点混乱; 所以将前端主流技术做了一个书签整理,不求最多最全,但求最实用。 书签源码 书签导入浏览器效果截图showImg(https://segmentfault.com/img/bVbg41b?w=107...

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

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

    _Suqin 评论0 收藏0
  • 前端知识归纳

    摘要:继承性子标签会继承父标签样式优先级行内样式选择器类选择器标签选择器通配符继承机制创建了的元素中,在垂直方向上的会发生重叠。 技能考察: 一、关于Html 1、html语义化标签的理解; 结构化的理解; 能否写出简洁的html结构; SEO优化 a、理解:根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时 让浏览器的爬虫和...

    sixleaves 评论0 收藏0

发表评论

0条评论

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