剖析 Promise
之前在使用 Promise
时最多可能就是 new 一个对象出来,然后使用 then
,Promise.all
,Promise.resove
等这些,清楚 Promise
具备的几个状态,但可能很少如深入剖析熟悉 Promise 的实现,所以本文其实也是我深入学习 Promise
做的整理。
从概念和 API 了解 Promise
(一)、背景动机
Promise
初始动机就是为了解决 JavaScript 回调地狱的问题,我们来看,在没有 Promise
时,我们是如何通过 callback
来处理异步函数问题的。
function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
callback(null, JSON.parse(res));
});
}
上边的写法有以下问题:
- 函数中额外的
callback
参数带给我们的疑问:输入值是什么,和返回值是什么 - 它不按原始的控制流运行
JSON.parse(res)
抛出异常无法处理
我们需要处理 JSON.parse
的异常,并且还要担心 callback
函数的异常,所以我们就有了糟糕的处理错误的代码:
function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
try {
res = JSON.parse(res);
} catch (ex) {
return callback(ex);
}
callback(null, res);
});
}
除了糟糕的异常处理代码外,callback
回调的参数也是约定多余了。我们需要记住callback的第一个参数是异常原因,第二个是成功结果。 Promises 帮助我们比较自然的处理异常错误,书写更简洁的代码而不是通过 callback
这种参数。
(二)、什么是 promise?
promises 的核心理念是,promise
代表着异步操作的结果值。promise
有三种不同的状态:
pending
: promise的初始值fulfillled
: promise 操作成功的状态rejected
: promise 操作失败的状态
一旦 promise 从 pending 转变到 fulfilled
或者 rejected
,它就是永久不可变的了。
(三)、构造一个 promise
使用 new Promise
的方式去构建一个 promise 。 通过传入一个真正处理逻辑的还是函数,该函数有两个参数并且会立即执行,第一个参数 是fulfills promise 的函数,第二个参数是 rejects promise 的函数。一旦操作完成,就会调用对应的函数。
重写上面的 readFile
函数:
function readFile(filename, enc){
return new Promise(function (fulfill, reject){
fs.readFile(filename, enc, function (err, res){
if (err) reject(err);
else fulfill(res);
});
});
}
(四)、等待一个 promise 执行完成
使用 promise.done
来等待一个 promise 完成,重写readJSON
函数:
function readJSON(filename){
return new Promise(function (fulfill, reject){
readFile(filename, 'utf8').done(function (res){
try {
fulfill(JSON.parse(res));
} catch (ex) {
reject(ex);
}
}, reject);
});
}
这段代码里,已经不存在 callback
这样的奇怪额外的回调参数,但是还存在很多异常处理的代码。(思考如何优化这段代码到更精简的程度)
(五)、转换和链式 Transformation / Chaining
.then
是可以链式的方式编程的,简单的讲,.then
和 .done
的区别和 .map
与.forEach
类似,换一种说法, 使用 .then
就是你打算用promise返回的结果去处理任何逻辑,而 .done
则是你不计划处理结果result。
现在,简单重写一下原始的列子:
function readJSON(filename){
return readFile(filename, 'utf8').then(function (res){
return JSON.parse(res);
});
}
由于 JSON.parse
是一个函数,我们可以改写成:
function readJSON(filename){
return readFile(filename, 'utf8').then(JSON.parse);
}
(六)、API 参考 API Reference
Promise.resolve(value)
返回一个 resolved 值为 value
的 promise。
如果 value
类似为promise,讲会执行此 promise 并得到 resolved 结果作为返回 promise 的 resolved 值。这对用来转换其他函数或库创建的 promise 很有用处。
Example
Promise.resolve("Success").then(function(value) {
console.log(value); // "Success"
}, function(value) {
// not called
});
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
console.log(v[0]); // 1
});
var original = Promise.resolve(true);
var cast = Promise.resolve(original);
cast.then(function(v) {
console.log(v); // true
});
Polyfill
代码执行环境如果不支持则简单实现:
Promise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject(value)
返回与给定的 promise 被拒绝的理由。
Example
Promise.reject(new Error("fail")).then(function(error) {
// not called
}, function(error) {
console.log(error); // Stacktrace
});
Polyfill
代码执行环境如果不支持则简单实现:
Promise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
Promise.all(iterable)
函数返回一个 Promise ,函数执行后会等待在 iterable 数组中的所有 promises 完成 fulfilled 状态时,返回一个数组结果,该数组元素一一对应每个promise 的结果值。
Example
var promise = Promise.resolve(3);
Promise.all([true, promise]).then(values => {
console.log(values); // [true, 3]
});
Polyfill
若执行环境不支持Promise.all,可以通过polyfill实现,实现1:
Promise.all = function (arr) {
// TODO: this polyfill only supports array-likes
// it should support all iterables
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
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;
if (--remaining === 0) {
resolve(args);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
实现2:
Promise.all = function (promises) {
var accumulator = [];
var ready = Promise.resolve(null);
promises.forEach(function (promise, ndx) {
ready = ready.then(function () {
return promise;
}).then(function (value) {
accumulator[ndx] = value;
});
});
return ready.then(function () { return accumulator; });
}
Promise.race(iterable)
返回一个 promise,当 iterable
有一个 promise 状态为 resolved
或 rejected
,就立马返回。
Example
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Both resolve, but p2 is faster
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then(function(value) {
console.log(value); // "three"
// p3 is faster, so it resolves
}, function(reason) {
// Not called
});
var p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then(function(value) {
// Not called
}, function(reason) {
console.log(reason); // "six"
// p6 is faster, so it rejects
});
Polyfill
Promise.race = function (values) {
// TODO: this polyfill only supports array-likes
// it should support all iterables
return new Promise(function (resolve, reject) {
values.forEach(function(value){
Promise.resolve(value).then(resolve, reject);
});
});
};
Promise.prototype.catch(onRejected)
等价于调用 Promise.prototype.then(undefined, onRejected)
。
Example
var p1 = new Promise(function(resolve, reject) {
resolve("Success");
});
p1.then(function(value) {
console.log(value); // "Success!"
throw "oh, no!";
}).catch(function(e) {
console.log(e); // "oh, no!"
});
p1.then(function(value) {
console.log(value); // "Success!"
throw "oh, no!";
}).then(undefined, function(e) {
console.log(e); // "oh, no!"
});
Polyfill
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.done(onFulfilled, onRejected) @non-standard
尚未标准化
var Promise = require('promise');
var p = Promise.resolve('foo');
p.done(function (value) {
console.log(value); // "foo"
});
p.done(function (value) {
throw new Error('Ooops!'); // thrown in next tick
});
Polyfill
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)
})
}
Promise.prototype.then(onFulfilled, onRejected)
和 .done 不同的是,.then 会返回一个promise
Example
var p1 = new Promise(function(resolve, reject) {
resolve("Success!");
// or
// reject ("Error!");
});
p1.then(function(value) {
console.log(value); // Success!
}, function(reason) {
console.log(reason); // Error!
});
var p2 = new Promise(function(resolve, reject) {
resolve(1);
});
p2.then(function(value) {
console.log(value); // 1
return value + 1;
}).then(function(value) {
console.log(value); // 2
});
Promise.prototype.finally(onResolved) @non-standard
finally()
方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then()
和catch()
中各写一次的情况。
var Promise = require('promise');
var p = Promise.resolve('foo');
var disposed = false;
p.then(function (value) {
if (Math.random() < 0.5) throw new Error('oops!');
else return value;
}).finally(function () {
disposed = true; // always called
}).then(function (value) {
console.log(value); // => "foo"
}, function (err) {
console.log(err); // => oops!
});
Polyfill
Promise.prototype['finally'] = function (f) {
return this.then(function (value) {
return Promise.resolve(f()).then(function () {
return value;
});
}, function (err) {
return Promise.resolve(f()).then(function () {
throw err;
});
});
}
最佳实践
详细见:https://www.promisejs.org/patterns/
从源码搞懂 Promise 实现机制
源码库为 then/promise
构建
从 package.json
可以看到,promise 库的构建入口为 build.js
文件,我们执行构建后,也发现多创建了三个目录 lib、setimmediate、domains
,相关目录的代码文件变动都和 asap
这个模块有关系。
可以看到,这里出来混淆代码之外,还将 asap/raw
模块分别换成了 asap
和 setImmediate
那这个 asap/raw
和 asap
还有 setimmediate
的区别是什么呢?
共同点,都是立即对参数中的函数进行异步调用
不同点:
- asap 比 setimmediate 调用更快,而且调用的时候会阻止其他事件的处理 (默认)。
- asap/raw 和 asap 运行的原理一样,但不处理运行抛出的异常 (换来更多效率), 同时也支持不同域的事件绑定。https://www.npmjs.com/package/asap
- setimmediate 为JS自带的,但它是在当前所有I/O事件完成后去调用,速度上没有ASAP快。
所以 Promise 有额外的 promise/domains
(支持domain) 和 promise/setimmediate
(支持自定义setimmediate) 供调用。
代码
核心代码是 core.js
,所以这里只贴出这里的解析。
'use strict';
var asap = require('asap/raw');
function noop() {}
// States:
//
// 0 - pending
// 1 - fulfilled with _value
// 2 - rejected with _value
// 3 - adopted the state of another promise, _value
//
// once the state is no longer pending (0) it is immutable
// All `_` prefixed properties will be reduced to `_{random number}`
//// build的时候会把所有的预定义的属性转变为 `_{随机数}的形式做混淆,不鼓励直接使用他们,看build.js中的fixup混淆函数就懂了
// at build time to obfuscate them and discourage their use.
// We don't use symbols or Object.defineProperty to fully hide them
// because the performance isn't good enough.
// to avoid using try/catch inside critical functions, we
// extract them to here.
var LAST_ERROR = null; // 记录最新错误
var IS_ERROR = {}; // 错误标记符,什么值都可行,能作为唯一识别参考即可
// 获取then方法
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
// 执行函数fn,并传入参数a,这里目的是做好统一的异常错误处理
function tryCallOne(fn, a) {
try {
return fn(a); // 返回执行结果值
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR; // 返回异常
}
}
// 执行fn函数并传入参数,目的一样是统一处理好异常
function tryCallTwo(fn, a, b) {
try {
fn(a, b); // 无返回
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR; // 有异常则返回异常
}
}
module.exports = Promise;
// Promise 构造器
function Promise(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
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;
// noop是作为某种情况的控制,不需要再执行 doResolve(传入空函数直接返回)
if (fn === noop) return;
// 开始正常流程处理
doResolve(fn, this);
}
Promise._onHandle = null;
Promise._onReject = null;
Promise._noop = noop;
// 原型链方法 then
Promise.prototype.then = function(onFulfilled, onRejected) {
// 如果this不是Promise 实例,则重新创建一个新的promise
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
// 创建一个空回调逻辑promise对象,用来创建 Handler
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
// safeThen 的作用是当调用 then 的时候环境 this 已经不是 Promise 的情况下能够继续安全执行 then
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));
});
}
/**
* 处理器函数,根据state的值来决定要做的事情
* @param {*} self
* @param {*} deferred
*/
function handle(self, deferred) {
// 当我们 resolve 接收到得是一个 promise 或 thenable 对象时,我们进入到 handle 后,会进入while循环,
// 直到 self 指向接收到的 promise,以接收到的 promise 的结果为标准
while (self._state === 3) {
self = self._value;
}
if (Promise._onHandle) { // 外部如果定义了 _onHandle的话这里处理一下
Promise._onHandle(self);
}
if (self._state === 0) {
// 在接收到的 promise 的 state===0 阶段我们会将原始 promise 中拿到得 onFulfilled 以及 onRejected 回调方法(包含在deferred对象中),
// 添加到接收到的 promise 的 _deferreds 中,然后return
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2; // 这里的1,2不像_state的意义,仅仅是作为记录_deferreds的个数,然后在finale里边用到
self._deferreds = [self._deferreds, deferred];
return;
}
self._deferreds.push(deferred);
return;
}
// finale 函数进来的都不为0直接走这里了
handleResolved(self, deferred);
}
/**
* 这个函数执行完,promise 的执行过程就完成了
* @param {*} deferred 这里的deffered中的promise是在then的时候创建的空promise,什么都不会执行(直接进入 finale 无handle情况)
*/
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);
// cb执行结果与异常处理
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}
function resolve(self, newValue) {
// 这里依照标准的promise执行程序
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) {
// 一个Promise的解决结果不能是自身,不然会出现循环处理的情况
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
// 值存在并且类型为对象或者函数时
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
// 是否存在then函数
var then = getThen(newValue);
// 这里处理的是获取then的时候异常情况
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
// 结果是promise 对象,则状态跟着这个promise走
if (
then === self.then &&
newValue instanceof Promise
) {
self._state = 3; // 3表示结果还是promise的情况
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
// 如果结果是一个包含then函数的对象(thenable),则继续走doResolve(基于then函数)
doResolve(then.bind(newValue), self);
return;
}
}
// newValue没什么特殊的,正常逻辑
self._state = 1; // fulfilled 状态
self._value = newValue;
finale(self);
}
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
if (Promise._onReject) {
Promise._onReject(self, newValue);
}
finale(self);
}
/**
* 可以总结出在三种情况下调用了finale:
* 1、_state=3,等待其他promise的结果时
* 2、_state=1,完成的时候
* 3、_state=2,reject的时候
*
* 所以,只有在 promise 结束或者依赖其他 promise 的时候,才会进入finale.
* 功能:该函数主要为了取出之前放入的deffereds,调用handle,走finale逻辑时_state都非0,所以进入handle时,直接走了handleResolved
*/
function finale(self) {
if (self._deferredState === 1) { // 单deffered处理逻辑
handle(self, self._deferreds);
self._deferreds = null;
}
if (self._deferredState === 2) { // 多deffered处理多级
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}
// Handler 类构造器,仅是包装 onFulfilled, onRejected, promise到一个实例上,deferred
function Handler(onFulfilled, onRejected, promise){
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*
*/
function doResolve(fn, promise) {
var done = false;
// 同步的直接调用传入的函数,将两个function作为fn的参数传入,也就是外部new Promise时编写的 resolve 和 reject
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
// 处理 resolve 逻辑
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
// 处理 reject 逻辑
reject(promise, reason);
});
// 如果fn执行出现异常则直接reject
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
/* Promise执行流程总结:
- 创建 Promise (new Promise)
- 设置需要执行的函数 (外部的resolve,reject)
- 设置完成的回调 (then调用后,通过handle处理deffered)
- 开始执行函数 (finale)
- 根据执行结果选择回调 (handleResolved) */
三个思考题就彻底理解 Promise 了:
- Promise 中为什么要引入微任务?
- Promise 中是如何实现回调函数返回值穿透的?
- Promise 出错后,是怎么通过“冒泡”传递给最后那个捕获异常的函数?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论