Promise超详细源码解读

说到promise,相信大家在日常开发中都经常使用到,它是我们异步操作中必不可少的一部分,可以让代码看起来变得更好理解;

我曾在技术社区看过许多关于promise底层原理的文章,大概原理明白,这次,我准备系统的分析实现源码并记录下来,本文将一行行代码去分析最后附加流程图和总结,希望这能对你有帮助;

promise的实现库有这么多,接下来我们以github的promise ployfill为例, 想看完整实现代码的在这里

最基础用法

我们先想象一下promise最常见的使用方式,大概像这样

new Promise((resolve,reject) => {
    setTimeout( () => resolve('result'),1000)
}).then( (value) => {
    console.log( value )
})
 

首先是new Promise(),那么Promise肯定是一个定好的构造函数,果然。

Promise Func

function Promise(fn) {
  if (!(this instanceof Promise))
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function') throw new TypeError('not a function');
  this._state = 0;
  this._handled = false;
  this._value = undefined;
  this._deferreds = [];
  doResolve(fn, this);
}
 
  1. 可以看到这里大致限制了必须要使用new操作符调用Promise,还有我们的参数必须是函数类型的;
  2. 初始化属性(_state,__handled,_value,_deferreds),这些属性后面要用到;
  1. 先看一下_state
/** 内部状态码
   * 0: pending,当前Promise正在执行中,默认值
   * 1: fulfilled, 执行了resolve函数,且参数_value不是期约,即_value instanceof Promise === false
   * 2: rejected,执行了reject函数
   * 3: fulfilled,执行了resolve函数,且参数_value是期约,即_value instanceof Promise === true
*/
 
  1. _value为存放我们最终resolve或reject的结果
  2. _deferreds缓存then方法执行后生成的handle实例对象,把传入的回调缓存成handle实例对象的属性
  1. 调用doResolve(fn, this);

可以看到我们把fn和this传递给了doResolve执行,接着看看doResolve;

doResolve Func

function doResolve(fn, self) {
  var done = false;
  try {
    fn(
      function(value) {
        if (done) return;
        done = true;
        resolve(self, value);
      },
      function(reason) {
        if (done) return;
        done = true;
        reject(self, reason);
      }
    );
  } catch (ex) {
    if (done) return;
    done = true;
    reject(self, ex);
  }
}

可以看到doResolve很简单,它直接调用了我们new Promise(fn)传递进去的fn,把两个匿名函数传递给了resolve,reject给我们在外部操作完成后调用,然后如果有出错会直接帮我们调用reject;

// 可以看回我们new Promise(fn)传递进去的fn
(resolve,reject) => {
    setTimeout( () => resolve('result'),1000)
}
 

执行fn,我们的setTimeout在1000ms后调用了resolve('result');

传递给fn的resolve是

function(value) {
	if (done) return;
	done = true;
	resolve(self, value);
}
 

这里的done实际上是为了防止我们多次调用resolve,只有第一次生效吧;

由于我们是异步调用resolve的,执行完setTimeout之后就会执行then,1000ms后才会调用resolve [我们往then Func里面看](#then Func) 由于我们fn里只调用了resolve,接下来我们直接看resolve

resolve Func

function resolve(self, newValue) {
  try {
    if (newValue === self)
      throw new TypeError('A promise cannot be resolved with itself.');
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
      var then = newValue.then;
      if (newValue instanceof Promise) {
        self._state = 3;
        self._value = newValue;
        finale(self);
        return;
      } else if (typeof then === 'function') {
        doResolve(bind(then, newValue), self);
        return;
      }
    }
    self._state = 1;
    self._value = newValue;
    finale(self);
  } catch (e) {
    reject(self, e);
  }
}
 

resolve主要是校验我们newValue的类型,我们的newValue是'result',自然是string

  1. self是通过Promise Func -> doResolve Func,传递下去的this,也就是首先判断的是newValue不能是new Promise的this本身
  2. newValue是object || function类型
  1. newValue是Promise实例,像我们上面说的_state会是3(),_把newValue赋值给我们最终resolve的结果,并执行finale(self);
  2. newValue.then是函数,会重新执行doResolve,不会执行finale;
  1. newValue不是Promise实例,newValue.then也不是函数,_state会是1(),把newValue赋值给我们最终resolve的结果,并执行finale(self);

很显然,我们的例子里resolve的类型效验会执行以上list->3,我们看看finale

finale Func

function finale(self) {
  if (self._state === 2 && self._deferreds.length === 0) {
    Promise._immediateFn(function() {
      if (!self._handled) {
        Promise._unhandledRejectionFn(self._value);
      }
    });
  }

  for (var i = 0, len = self._deferreds.length; i < len; i++) {
    handle(self, self._deferreds[i]);
  }
  self._deferreds = null;
}

_state为1,finale代码会执行

for (var i = 0, len = self._deferreds.length; i < len; i++) {
    handle(self, self._deferreds[i]);
}
self._deferreds = null;
 

😅呃...循环了then Func -> handle Func push进去的deferred,又重新拿着deferred执行handle Func??

这里跟我们在then Func的调用方式好像没有什么不同,只是我们调用完resolve Func后_state和_value就有值了

self._state = 1;
self._value = newValue;
 

[去看handle Func](#handle Func)

then Func

function noop() {}
Promise.prototype.then = function(onFulfilled, onRejected) {
  // @ts-ignore
  var prom = new this.constructor(noop);

  handle(this, new Handler(onFulfilled, onRejected, prom));
  return prom;
};
 

可以看到then是在Promise原型链上的方法

noop

noop是一个空函数,这里的this.constructor即Object.constructor;

即new Object.constructor(function noop() {});

😂...赶紧去控制台试试看;

(function anonymous(
) {
function noop(){}
}

呃...实际上我们得到一个包装好参数的函数,把它赋值为prom

handle

首先handle(this, new Handler(onFulfilled, onRejected, prom));

new Handler(onFulfilled, onRejected, prom)

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

这个函数还是挺好理解的,new Handler创建一个handler对象,这里的this也就是handler;

handler下有三个属性,onFulfilled, onRejected是我们调用promise.then(onFulfilled, onRejected)是传递进来的,promise也就是我们刚刚赋值的prom函数

然后就是handle(this, handler),当前的的this为当前的new promise对象,handler为刚刚的new Handler实例对象

handle Func

function handle(self, deferred) {
  while (self._state === 3) {
    self = self._value;
  }
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  self._handled = true;
  Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    resolve(deferred.promise, ret);
  });
} 

如果是then Func调用的handle Func,我们到当前为止并没有改变_state,即为0,直接self._deferreds.push(deferred)后return了

if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
}

到这里为止,我们的then Func一系列的调用就完毕了,1000ms后会调用resolve,[我们看回resolve Func](#resolve Func)

如果是从resolve Func -> finale Func调用的handle Func,那么_state和_value就有值了,在我们例子里_state为1,_value为'result',会执行

self._handled = true;
Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
    	(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
    	return;
    }
    var ret;
    try {
    	ret = cb(self._value);
    } catch (e) {
    	reject(deferred.promise, e);
    	return;
    }
    resolve(deferred.promise, ret);
});

Promise._immediateFn的参数会在不久后执行,我们先看内容,后面再详细看这个

根据_state判断调用deferred.onFulfilled || deferred.onRejected,我们说_state有四个值,忘记的可以回去上面看看,代码执行到这里state的值只有1 || 2;

deferred为我们调用then Func->handle->push进去的handle实例化对象;

到ret = cb(self._value);为止,我们整个new Promise((resolve,reject) => {}).then(() => {})就走完了,可以看到是通过_state值去判断调用我们then Func传递进去的两个函数的

回到immediateFn

var setTimeoutFunc = setTimeout;
Promise._immediateFn =
  // @ts-ignore
  (typeof setImmediate === 'function' &&
    function(fn) {
      // @ts-ignore
      setImmediate(fn);
    }) ||
  function(fn) {
    setTimeoutFunc(fn, 0);
  };
 

呃...阿这..😲Promise._immediateFn实际上是使用setTimeout执行的,由于即使设定timeout为0,setTimeout也会有一定的延时,这样哪怕我们在fn中直接执行了resolve Func,执行顺序也会比then Func晚;

重新梳理思维导图

例子1

new Promise((resolve,reject) => {
    resolve('result')
}).then( (value) => {
    console.log( value )
})
 

JavaScript Promise超详细源码解读_数组

例子2

new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('result')
    },1000)
}).then( (value) => {
    console.log( value )
})

JavaScript Promise超详细源码解读_数组_02

例子3

var promise = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('result')
    },1000)
});
setTimeout(() => {
    promise.then((val) => {
    	console.log('then2' + val)
	})
},500)
setTimeout(() => {
    promise.then((val) => {
    	console.log('then4' + val)
	})
},1200)
 
 

JavaScript Promise超详细源码解读_赋值_03

看到这里大家也许都明白了,在Promise实例上调用then必定会new Handler把传入的回调暂存到实例对象里,再调用handle传入Handler实例,handle会根据this._state状态执行不同的操作

_state为0则把handle实例push到this.__deferreds,并return终止函数,等待resolve函数执行在循环this.__deferreds重新调用handle;

_state为1则表示已调用resolve,此时直接延时(setTimeout 0)执行handle实例的onFulfilled

Promise.all实现

Promise.all = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!isArray(arr)) {
      return reject(new TypeError('Promise.all accepts an array'));
    }

    var args = Array.prototype.slice.call(arr);
    if (args.length === 0) return resolve([]);
    var remaining = args.length;

    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          var then = val.then;
          if (typeof then === 'function') {
            then.call(
              val,
              function(val) {
                res(i, val);
              },
              reject
            );
            return;
          }
        }
        args[i] = val;
        if (--remaining === 0) {
          resolve(args);
        }
      } catch (ex) {
        reject(ex);
      }
    }

    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
}; 

这个应该和大多数人想的都差不多,实际上就是返回了一个新的Promise实例,remaining记录传递进来的Promise实例数组长度,再循环传递进来的Promise实例数组,调用每个实例的then方法,传入的回调函数把结果push到一个临时数组,再把remaining--,当remaining为0时,就可以调用返回出去的Promise实例的resolve方法把临时数组传递进去了;

这里需要注意,就是上面代码的判断条件,我们平时使用Promise.all时,Promise元素resolve的值应避免是对象或函数且拥有then方法

比如,我们的预期是resolve一个带有then方法的对象;

resolve({
    then: () => {console.log('xxxxxx')}
});
 

但Promise.all并不会达到我们所要预期的结果;Promise.all().then里的回调函数永远不会执行;

看到这里,你可以去猜测一下提问环节中的第三个例子的执行结果了;

可以试着猜想一下,但千万不要复制到控制台执行!!!

可以试着猜想一下,但千万不要复制到控制台执行!!!

可以试着猜想一下,但千万不要复制到控制台执行!!!

提问环节

后续会公布答案和原理 console.log的顺序是什么?为什么??

new Promise( (resolve,reject) => { 
    console.log('resolve.run'); 
    resolve('test'); 
}).then( (val) => {
    console.log(val)
})
 
var promise = new Promise( (resolve,reject) => { 
    console.log('resolve.run'); 
    resolve('test'); 
});
setTimeout(() => {
    promise.then( (val) => {
    	console.log(val)
	})
},0)
 

以下Promise.all的结果是什么??

var promise = new Promise( (resolve,reject) => { window.resolve = resolve; window.reject = reject })
Promise.all([promise]).then((result) => console.log(result) )
var then = { then: (fn) => { console.log('test...');fn(then) } }
resolve(then)

 

 

出处:https://blog.51cto.com/u_15590807/9150482

posted on 2024-01-14 19:54  jack_Meng  阅读(525)  评论(0编辑  收藏  举报

导航