前言

then/promise项目是基于Promises/A+标准实现的Promise库,从这个项目当中,我们来看Promise的原理是什么,它是如何做到的,从而更加熟悉Promise

分析

从index.js当中知道,它是先引出了./core.js,随后各自执行了其他文件的代码,通过requeire的方法。

我们首先先想一下最基础的promise用法


new Promise((resolve, reject) => {
resolve(4); }).then(res => {
console.log(res); // export 4
});

Promise中的标准

标准中规定:

  1. Promise对象初始状态为 Pending,在被 resolvereject 时,状态变为 FulfilledRejected
  2. resolve接收成功的数据,reject接收失败或错误的数据
  3. Promise对象必须有一个 then 方法,且只接受两个可函数参数 onFulfilledonRejected

index.js


'use strict'; module.exports = require('./core.js');
require('./done.js');
require('./finally.js');
require('./es6-extensions.js');
require('./node-extensions.js');
require('./synchronous.js');

我们先看src/core.js


function Promise(fn) {
// 判断 this一定得是object不然就会报错,这个方法一定得要new出来
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
// 判断fn 一定得是一个函数
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
this._deferredState = 0;
this._state = 0;
this._value = null;
this._deferreds = null;
if (fn === noop) return;
// 最终doResolve很关键
doResolve(fn, this);
}

Promise是一个构造方法,开始时,它进行了校验,确保了fn是一个函数,随后对一些变量进行了初始化,最后执行了doResolve()

我们接着看doResolve这个方法。


/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
//
// 确保`onFulfilled`和`onRejected`方法只调用一次
// 不保证异步
function doResolve(fn, promise) {
var done = false;
var res = tryCallTwo(fn, function (value) {
// 如果done 为true 则return
if (done) return;
done = true;
// 回调执行 resolve()
resolve(promise, value);
}, function (reason) {
// 如果done 为true 则return
if (done) return;
done = true;
reject(promise, reason);
});
// res为truCallTwo()的返回值
// 如果done没有完成 并且 res 是 `IS_ERROR`的情况下
// 也会执行reject(),同时让done完成
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}

doResolve最关键的是执行了tryCallTwo方法,这个方法的第二,第三个参数都是回调,当执行回调后,done为true,同时各自会执行resolve()或者reject()方法。最后当tryCallTwo的返回值为IS_ERROR时,也会执行reject()方法。

我们先来看一下tryCallTwo方法


function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}

fn实际就是Promise初始化时的匿名函数(resolve, reject) => {}ab则代表的是resolve()reject()方法,当我们正常执行完promise函数时,则执行的是resolve则在doResolve中,我们当时执行的第二个参数被回调,如果报错,reject()被执行,则第二个参数被回调。最后捕获了异常,当发生了报错时,会return IS_ERROR,非报错时会return undinfed

再回到刚才的doResolve方法,当执行了第二个参数的回调之后,会执行resolve方法


function resolve(self, newValue) {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
// 不能吃传递自己
if (newValue === self) {
// 报错
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
// promise作为参数
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
// 获取它的promise方法 读取newValue.then
var then = getThen(newValue);
if (then === IS_ERROR) {
// 如果then IS_ERROR
return reject(self, LAST_ERROR);
}
if (
// 如果then是self的then
// 并且Promise
then === self.then &&
// newValue 属于Promise
newValue instanceof Promise
) {
// _state为3
// 一般then之后走这里
// 执行then(newValue)返回了promise
self._state = 3;
// selft.value为newValue
self._value = newValue;
// 当state为3时执行 finale
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}

在没有链式调用then的情况下(也就是只要一个then)的情况下,会将内部状态_state设置成3,将传入值赋给内部变量_value最后会执行final()方法,不然则会使用doResolve来调用then

我们再来看下reject


function reject(self, newValue) {
// _state = 2为reject
self._state = 2;
self._value = newValue;
if (Promise._onReject) {
Promise._onReject(self, newValue);
}
finale(self);
}

reject当中我们的_state变更为了2,同样最后finale被调用。

我们来看下finale函数


// 执行自己的deferreds
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
handle(self, self._deferreds[i]);
}
// 将deferred 置空
self._deferreds = null;
}
}

在该方法当中根据不同的_deferredState,会执行不同的handle方法。

我们再来看handle方法


function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
// 如果有onHandle方法 则执行该方法
if (Promise._onHandle) {
Promise._onHandle(self);
}
// (初始 _state 为0)
if (self._state === 0) {
// (初始 _deferredState 为0)
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
// 如果 _deferredState是1 则__deferreds是一个数组
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
// 当走到这里 _deferredState应该是2 将deferred
// 插入到数组当中
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}

这里比较关键的应该就是通过deferredState不同的状态,将deferred放入deferreds当中。另外当我们的_state不为0时,最终会执行handleResolved

继续看handleResolve()方法


function handleResolved(self, deferred) {
asap(function() {
// _state为1时,cb = onFulfilled 否则 cb = onRejected
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 {
resolve(deferred.promise, ret);
}
});
}.then((res) => {
}).catch((error) => {
})

在这个方法当中,会根据我们任务(_state)的不同状态,来执行onFulfilled或者onRejected方法。当此方法调用时,也就是我们一个简单的Promise的结束。

回到刚才说的Promise构造方法结束的时候

设置了Promise函数的一些变量


Promise._onHandle = null;
Promise._onReject = null;
Promise._noop = noop;

随后在Promise的原型上设置了then方法。


Promise.prototype.then = function(onFulfilled, onRejected) {
// 首先看这是谁构造的 如果不是promise
// 则return 执行safeThen
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
// 如果是则初始化一个Promise 但是参数 noop 为空对象 {}
var res = new Promise(noop);
// 随后执行handle方法
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};

then这个方法中首先判断了它是否由Promise构造的,如果不是,则返回并执行safeThen,不然则执行Promise构造一个res对象,然后执行handle方法,最后将promise变量res返回。handle方法之前有提过,在这里,当初始化时_state_deferred的转改都为0,因此它会将defrred保存到promise当中。

先看一下上面说的safeThen方法


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));
});
}

流程

需要有一个Promise的构造方法,这个构造方法最终会执行它的参数(resolve, reject) => {},声明的then方法会通过handle()方法将onFulfilledonRejected方法保存起来。当在外部调用resolve或者onRejected时,最终也会执行handle但是它,会最后根据状态来执行onFulfilled或者onRejected。从而到我们的then回调中。

Promise的扩展

done

done的扩展在src/done.js当中


'use strict'; var Promise = require('./core.js'); module.exports = Promise;
Promise.prototype.done = function (onFulfilled, onRejected) {
var self = arguments.length ? this.then.apply(this, arguments) : this;
self.then(null, function (err) {
setTimeout(function () {
throw err;
}, 0);
});
};

内部执行了then()

finally

finally的扩展在src/finally.js当中

Promise的标准当中,本身是没有finally方法的,但是在ES2018的标准里有,finally的实现如下


'use strict'; var Promise = require('./core.js'); module.exports = Promise;
Promise.prototype.finally = function (callback) {
return this.then(function (value) {
return Promise.resolve(callback()).then(function () {
return value;
});
}, function (err) {
return Promise.resolve(callback()).then(function () {
throw err;
});
});
};

PromiseonFulfilledonRejected 不管回调的哪个,最终都会触发callback 回调。还要注意的一点是finally的返回也是一个Promise

es6-extensions.js

es6-extensions.js文件当中包含了ES6的一些扩展。

Promise.resolve


function valuePromise(value) {
var p = new Promise(Promise._noop);
// 将_state赋值为 非0
// _value进行保存
p._state = 1;
p._value = value;
// 这样做的目的是省略的一些前面的逻辑
return p;
} Promise.resolve = function (value) {
if (value instanceof Promise) return value; if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING; // value return new Promise
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
// 返回 返回了一个新的Promise对象
return new Promise(then.bind(value));
}
} catch (ex) {
// 如果报错 则返回一个就只
return new Promise(function (resolve, reject) {
reject(ex);
});
}
} return valuePromise(value);
};

Promise.reject


Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};

Promise.all


Promise.all = function (arr) {
// 类似深拷贝了一份给了args
var args = Array.prototype.slice.call(arr); return new Promise(function (resolve, reject) {
// 判断了all的promise数量
if (args.length === 0) return resolve([]);
// remaining则是promise数组的长度
var remaining = args.length;
// i为index val 为 promise
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
if (val instanceof Promise && val.then === Promise.prototype.then) {
while (val._state === 3) {
val = val._value;
}
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// val._state 为 0时 走这里
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
// 当所有的promise执行完 则是remaining为0
// 则执行resolve();
if (--remaining === 0) {
resolve(args);
}
}
// 遍历所有的promise
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};

Promise.all()返回的也是一个Promise函数。
内部有一个remaining变量每当执行完一个promise函数后就会减一,当所有promise执行完,会执行自己的resolve

Promise.race


Promise.race = function (values) {
return new Promise(function (resolve, reject) {
values.forEach(function(value){
Promise.resolve(value).then(resolve, reject);
});
});
};

遍历传入的promise数组,经过Promise.resolve(value)的源码可以看到,如果value是一个Promise则户直接将这个value返回,最后数组中的promise哪个优先回调即执行。

Promise.property.catch

catch在标准当中也是没有,虽然我们用的比较多


Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};

catch的回调实际是then(null, onRejected)的回调。

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

来源:https://segmentfault.com/a/1190000017475467

Promise 源码分析的更多相关文章

  1. jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...

  2. 移动web app开发必备 - Deferred 源码分析

    姊妹篇  移动web app开发必备 - 异步队列 Deferred 在分析Deferred之前我觉得还是有必要把老套的设计模式给搬出来,便于理解源码! 观察者模式 观察者模式( 又叫发布者-订阅者模 ...

  3. jQuery 2.0.3 源码分析 Deferred概念

    JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而回调函数中则包含了后续的工作.这也是造成异步编程困难的主要原因:我们一直习惯于“线性”地编写代码 ...

  4. jQuery.queue源码分析

    作者:禅楼望月(http://www.cnblogs.com/yaoyinglong ) 队列是一种特殊的线性表,它的特殊之处在于他只允许在头部进行删除,在尾部进行插入.常用来表示先进先出的操作(FI ...

  5. basket.js 源码分析

    basket.js 源码分析 一.前言 basket.js 可以用来加载js脚本并且保存到 LocalStorage 上,使我们可以更加精准地控制缓存,即使是在 http 缓存过期之后也可以使用.因此 ...

  6. jQuery 2.0.3 源码分析 Deferrred概念

    转载http://www.cnblogs.com/aaronjs/p/3348569.html JavaScript编程几乎总是伴随着异步操作,传统的异步操作会在操作完成之后,使用回调函数传回结果,而 ...

  7. whatwg-fetch源码分析

    fetch 是什么 XMLHttpRequest的最新替代技术 fetch优点 接口更简单.简洁,更加语义化 基于promise,更加好的流程化控制,可以不断then把参数传递,外加 async/aw ...

  8. 一个普通的 Zepto 源码分析(二) - ajax 模块

    一个普通的 Zepto 源码分析(二) - ajax 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块,以 ...

  9. co源码分析及其实践

    本文始发于我的个人博客,如需转载请注明出处. 为了更好的阅读体验,可以直接进去我的个人博客看. 前言 知识储备 阅读本文需要对Generator和Promise有一个基本的了解. 这里我简单地介绍一下 ...

随机推荐

  1. js-jquery 中$.ajax -浅显接触

    工作了将近2年,终于开始自己写ajax了!!!真紧张的! 当年培训时就没有学ajax,就让我们自己看看,我是那种主动学习的人吗?不是!!!所以搞不懂ajax!!!!! 在工作中,数据的绑定我们之前都是 ...

  2. Hibernate游记——装备篇《三》(连接池的使用)

    这里介绍几种最常见的连接池配置: [说明:在hibernate3.0中,已经不再支持dbcp了,hibernate的作者在hibernate.org中,明确指出在实践中发现dbcp有BUG,在某些种情 ...

  3. 构建伪Update服务器工具isr-evilgrade

    构建伪Update服务器工具isr-evilgrade   现在大部分软件都提供更新功能.软件一旦运行,就自动检查对应的Update服务器.如果发现新版本,就会提示用户,并进行下载和安装.而用户往往相 ...

  4. 2017 [六省联考] T2 相逢是问候

    4869: [Shoi2017]相逢是问候 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 1205  Solved: 409[Submit][Stat ...

  5. 1005 Spell It Right

    1005 Spell It Right   Given a non-negative integer N, your task is to compute the sum of all the dig ...

  6. gradle_____最后到齐的构建工具

    从今年开始,开始换用gradle 了,个人感觉还好,配置不像maven,一堆xml 文件,一个jar 一行字符,内置的task 和很多.自定义task 也挺简单,比ant简单一些. 简单配置文件示例: ...

  7. 【spring mvc】后台的API,测试中,总提示接口实体的某一个字段不能为null,但是明明给值了还提示不能为空

    实体是这三个字段 接口的实现类Controller 前台测试给值 依旧报错 解决方法: 需要添加@RequestBody注解

  8. 【Linxu】CentOS7下安装程序报错:

    进入root用户,然后编辑 vi /usr/libexec/urlgrabber-ext-down 将首行换成 #!/usr/bin/python2.

  9. Dance In Heap(二):一些堆利用的方法(上)

    0×00 前面的话 在前面的文章里我们稍微有点啰嗦的讲解了堆中的一些细节,包括malloc.free的详细过程,以及一些检查保护机制,那在这篇文章里,我们就开始结合这些机制,以64位为例来看一看如何对 ...

  10. Linux中的热键[Tab] [Ctrl]-c [Ctrl]-d

    Tab键:命令或者文件补全.可以避免很多的输入错误 1. 按一次,文件或命令补全 2. 按两次,会列举出以按键前的字母为首的所有命令或者文件 Ctrl+C:中断目前程序 Ctrl+D:键盘输入结束.可 ...