博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
老生常谈:Promise 用法与源码分析
阅读量:6690 次
发布时间:2019-06-25

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

此文章是几个月前写得,发现没有发表过,就在此发表一下。

背景

Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景。以前,处理异步最常用的方法就是回调函数,但是当过程稍微复杂一点,多个异步操作集中在一起的时候,就容易出现一个回调金字塔的情况,可读性和可维护性都非常差,比如:

setTimeout(function () {  console.log('ping');  setTimeout(function () {    console.log('pong');    setTimeout(function () {      console.log('end!');    }, 1000);  }, 1000);}, 1000);复制代码

Promise可以避免这种情况发生,将回调嵌套转变为链式调用,避免回调金字塔的出现。

Promise基本用法

Promise有4种状态:

  • fulfilled——成功状态
  • rejected——失败状态
  • pending——执行状态(未成功也未失败)
  • settled——完成状态
let promise = new Promise((resolve, reject) => {  // when success, resolve  let value = 'success';  resolve(value);   // when an error occurred, reject  reject(new Error('Something happened!'));});复制代码

可以通过then方法来处理返回的结果

// promise.then(onResolve, onReject)promise.then(response => {  console.log(response);}, error => {  console.log(error);});复制代码

then方法不仅仅是处理结果,而且还可以继续返回promise对象

promise.then(response => {  console.log(response); // success  return 'another success';}).then(response => {  console.log(response); // another success });复制代码

对reject状态返回的结果的处理,可以通过then的第二个参数,也可以通过catch方法

promise.then(  null,  error => {    console.log(error); // failure  });// 或promise.catch(err => {  console.log(err); // failure});复制代码

同时处理多个promise,不关注执行顺序可以用all方法

let doSmth = new Promise(resolve => {  resolve('doSmth');}),doSmthElse = new Promise(resolve => {  resolve('doSmthElse');}),oneMore = new Promise(resolve => {  resolve('oneMore'); });Promise.all([  doSmth,  doSmthElse,  oneMore]).then(response => {  let [one, two, three] = response;  console.log(one, two, three); // doSmth doSmthElse oneMore});复制代码

Promise.all()接收一个promises数组,当全部fulfiled时,返回一个按顺序的数组 当其中一个reject时,返回第一个rejected的值 或者race方法,接收多个promise实例,组成一个新的promise,有一个变化的时候,外层promise跟着变化。 快捷方法:

  • Promise.resolve(value) 返回一个resolve(value)的promise 或直接返回这个value如果value本身时promise对象的话。
  • Promise.reject(value) 返回一个rejected状态的promise,并且reject(value)

——参考

原理

为了学习promise内部原理,最好是看其实现源码,是github上一个遵循promise A+规范的库,其核心代码在core文件中。那么就从这个库来学习。

function noop() {} // 定义一个空函数用于对比和实例化空promise,后面会用到// States://  库定义的4种状态// 0 - pending// 1 - fulfilled with _value// 2 - rejected with _value// 3 - adopted the state of another promise, _valuevar LAST_ERROR = null;  var IS_ERROR = {};   // 这两个用来捕获错误// 获取obj中的then方法function getThen(obj) {  try {    return obj.then;  } catch (ex) {    LAST_ERROR = ex;    return IS_ERROR;  }}// 当then中只传进了一个回调函数时调用此方法function tryCallOne(fn, a) {  try {    return fn(a);  } catch (ex) {    LAST_ERROR = ex;    return IS_ERROR;  }}// 当then中传入了两个回调函数时调用此方法function tryCallTwo(fn, a, b) {  try {    fn(a, b);  } catch (ex) {    LAST_ERROR = ex;    return IS_ERROR;  }}复制代码
// Promise构造函数function Promise(fn) {// 检验是否实例化了promise对象,不能直接使用promise构造函数来封装自己的代码  if (typeof this !== 'object') {    throw new TypeError('Promises must be constructed via new');  }// 检验传进来的是否为函数,promise必须接受一个函数来进行实例化  if (typeof fn !== 'function') {    throw new TypeError('Promise constructor\'s argument is not a function');  }  this._deferredState = 0;  // 与后面的this._deferreds关系密切,当resolve方法接收的是一个promise时,回用到他们  this._state = 0;  // 对应上方4种状态  this._value = null; // 存放最终结果  this._deferreds = null;  // 存放then中接收的处理函数  if (fn === noop) return;  // 如果promise接收的是空函数,直接返回,结束。  doResolve(fn, this);}复制代码

可以看到,我通过Promise构造函数实例化一个promise对象,在对参数进行检查后,我们会执行doResolve(fn, this)方法,顺藤摸瓜看看doResolve函数做了什么

function doResolve(fn, promise) {  var done = false; // 确保onFulfilled 和 onRejected只被调用一次  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);  }}复制代码

这里就是将两个回调函数分别传给 fn 的 两个参数,并确保他们只执行一次。 接下来就要看它的resolve方法。

function resolve(self, newValue) {  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);    if (then === IS_ERROR) {      return reject(self, LAST_ERROR);    }    if (      then === self.then &&      newValue instanceof Promise    ) {// 当接收的参数为promise,或thenable对象时。      self._state = 3;      self._value = newValue;      finale(self); // 执行_deferreds 中的方法,如果有的话。      return;    } else if (typeof then === 'function') {      doResolve(then.bind(newValue), self);      return;    }  }  self._state = 1;  self._value = newValue;  finale(self);}复制代码

resolve除了一些判断外,就是根据接收到的参数的类型来修改state的值。如果接收到promise对象或thenable对象,state转为3,并使用它的结果,如果时其他如字符串类型等,state转为1,直接使用该值。 有了结果之后,就要看一下then方法了。

Promise.prototype.then = function(onFulfilled, onRejected) {  if (this.constructor !== Promise) {    return safeThen(this, onFulfilled, onRejected);  }  var res = new Promise(noop);  handle(this, new Handler(onFulfilled, onRejected, res));  return res;};function safeThen(self, onFulfilled, onRejected) {  return new self.constructor(function (resolve, reject) {    var res = new Promise(noop);    res.then(resolve, reject);    handle(self, new Handler(onFulfilled, onRejected, res));  });}复制代码

then方法也很简单,就是用Handler包装一个对象

function Handler(onFulfilled, onRejected, promise){  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;  this.onRejected = typeof onRejected === 'function' ? onRejected : null;  this.promise = promise;}复制代码

然后调用handle方法。整个过程就是创建一个新的promise,调用handle方法,将新的promise返回,以便实现链式调用。 下面看一下handle方法。

function handle(self, deferred) {// self 移动指向最新的promise  while (self._state === 3) {    self = self._value;  }  if (Promise._onHandle) {    Promise._onHandle(self);  }  if (self._state === 0) {// 向_deferredState中添加handler处理过得对象,也就是{onFulfilled,onRejected,promise}    if (self._deferredState === 0) {      self._deferredState = 1;      self._deferreds = deferred;      return;    }    if (self._deferredState === 1) {      self._deferredState = 2;      self._deferreds = [self._deferreds, deferred];      return;    }    self._deferreds.push(deferred);    return;  }  handleResolved(self, deferred);}复制代码

handle方法就是根据state的值和_deferredState ,来决定要做的事情,我们来捋一捋,当我们的resolve方执行,state转为1时,我们会进入then方法,然后进入handle方法,因为state为1,可以看到我们会直接进入handleResolved方法。

resolve   ->  then  ->  handle  -> handleResolved复制代码

看看handleResolved函数是做什么的

function handleResolved(self, deferred) {  asap(function() {    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;    if (cb === null) {      if (self._state === 1) {        resolve(deferred.promise, self._value);      } else {        reject(deferred.promise, self._value);      }      return;    }    var ret = tryCallOne(cb, self._value);    if (ret === IS_ERROR) {      reject(deferred.promise, LAST_ERROR);    } else {// 此处主要服务于promise的链式调用,因为promise通过返回一个新的promise来实现链式调用。// 新的promise保存在deferred.promise中      resolve(deferred.promise, ret);    }  });}复制代码

过滤掉添加判断,handleResolved就是使用结果值self._value调用then中的相应回调(成功或失败)。 那当resolve接收的是普通值得时候整个运行过程就知道了。

resolve  ->  then  ->  handle  -> handleResolved -> 执行onFulfilled或onRejected复制代码

当我们resolve接收到得是一个promise或thenable对象时,我们进入到handle后,会进入while循环,直到self指向接收到的promise,以接收到的promise的结果为标准,在接收到的promise的 state===0 阶段我们会将原始promise中拿到得onFulfilled以及onRejected回调方法(包含在deferred对象中),添加到接收到的promise的 _deferreds 中,然后return。 存在 _deferreds 中的回调在什么时候执行呢? 我们可以看到无论时resolve还是reject,只要状态改变都会执行 finale 方法,我们看一下 finale

function finale(self) {  if (self._deferredState === 1) {    handle(self, self._deferreds);    self._deferreds = null;  }  if (self._deferredState === 2) {    for (var i = 0; i < self._deferreds.length; i++) {      handle(self, self._deferreds[i]);    }    self._deferreds = null;  }}复制代码

因为每次执行此方法都是在state状态改变的时候,所以进入handle函数后会直接进入handleResolved方法,然后使用self._value的结果值执行对应的回调函数(onFulfilled 或 onRejected)。 最后看看reject

function reject(self, newValue) {  self._state = 2;  self._value = newValue;  if (Promise._onReject) {    Promise._onReject(self, newValue);  }  finale(self);}复制代码

这下清晰多了,再来捋一捋,

总结

  • Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景,代替了传统基于回调的方案,防止了如回调金字塔等现象的发生。
  • promise内部运行机制:使用promise封装异步函数,通过resolve和reject方法来处理结果,
    • 当发生错误时,reject会将state转为状态2(rejected)并调用对应的onRejected回调函数,
    • 当成功时,resolve接收对应的结果,当结果时普通值(比如string类型)他会将state转为状态1,直接使用该值调用对应的onFulfilled回调函数,
    • 当接收到的是一个promise对象或者thenable对象时,会将thenable对象转为promise对象,并将当前state转为3,将我们的onFulfilled和onRejected回掉函数保存到接收到的promise中,并采用接收到的promise的结果为最终标准,当它的state发生变化时,执行相应的回调函数。
  • 其链式调用时通过返回一个新的promise空对象来实现的,在当前的onFulfilled或onRejected回调执行后,会将执行结果以及新的promise作为参数去调用onFulfilled或onRejected方法,实现值在链式中的传递。

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

你可能感兴趣的文章
Hessian HTTP POST访问时,Nginx返回411问题
查看>>
Exif图片方向的一些发现
查看>>
iOS之传值
查看>>
pandas 修改 DataFrame 列名
查看>>
《2018年云上挖矿态势分析报告》发布,非Web类应用安全风险需重点关注
查看>>
Nervos 双周报第 3 期:佛系新年之后的开工大吉!
查看>>
【PHP 扩展开发】Zephir 基础篇
查看>>
怎么将在线录制的视频转为GIF动态图
查看>>
【剑指offer】顺时针打印矩阵
查看>>
聊聊JavaScript和Scala的表达式 Expression
查看>>
CSS3中的box-sizing
查看>>
云计算新风向:多云战略优化企业云支出
查看>>
Windows改Linux(一),新建Ubuntu虚拟机小白向导
查看>>
HTML5调用手机前置摄像头或后置摄像头拍照,canvas显示,经过Android测试
查看>>
Spring Cloud构建微服务架构:分布式服务跟踪(入门)【Dalston版】
查看>>
【355天】跃迁之路——程序员高效学习方法论探索系列(实验阶段113-2018.01.26)...
查看>>
Rust编程语言的核心部件
查看>>
BZOJ 1061: [Noi2008]志愿者招募【单纯形裸题】
查看>>
v8世界探险(3) - v8的抽象语法树结构
查看>>
《C语言及程序设计》实践项目——用if语句实现分支结构
查看>>