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

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

背景

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

猜你喜欢

转载自juejin.im/post/5bc84ea8518825780324fabb