老生常谈:Promise 用法与源码分析

栏目: JavaScript · 发布时间: 6年前

内容简介:Promise本身是一个异步编程的方案,让处理过程变得更简单。es6引入promise特性来处理JavaScript中的异步场景。以前,处理异步最常用的方法就是回调函数,但是当过程稍微复杂一点,多个异步操作集中在一起的时候,就容易出现一个Promise可以避免这种情况发生,将Promise有4种状态:

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)

——参考ES6 Promise

原理

为了学习promise内部原理,最好是看其实现源码, then/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, _value

var 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 用法与源码分析

总结

  • 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方法,实现值在链式中的传递。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创投之巅——中国创投精彩案例

创投之巅——中国创投精彩案例

投资界网站 / 人民邮电出版社 / 2018-11 / 69.00

中国的科技产业发展,与创投行业密不可分。在过去的几十年间,资本与科技的结合,缔造了众多创业“神话”。回顾这些科技巨头背后的资本路径,可以给如今的国内创业者很多有益的启发。 本书从风险投资回报率、投资周期、利润水平、未来趋势等多个维度,筛选出了我国过去几十年中最具代表性的创业投资案例,对其投资过程和企业成长过程进行复盘和解读,使读者可以清晰地看到优秀创业公司的价值与卓越投资人的投资逻辑。一起来看看 《创投之巅——中国创投精彩案例》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具