此文章是几个月前写得,发现没有发表过,就在此发表一下。
背景
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方法,实现值在链式中的传递。