# 👉 Promise/A+ 实现原理
Promise 是前端异步编程解决方案之一。
Promises/A+ 规范中表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符。
# Promise/A+ 规范-实现
// 工具函数
const isObject = (obj) =>
Object.prototype.toString.call(obj) === "[object Object]";
const isFunction = (func) => typeof func === "function";
const isPromise = (func) => func instanceof _Promise;
// in 用于判断【指定的属性】是否在指定的对象或其原型链中
const isThenable = (obj) =>
(isObject(obj) || isFunction(obj)) && "then" in obj && isFunction(obj.then);
// 2.1.1 有三种状态
const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");
function _Promise(executor) {
// 确保handler是一个函数
if (!isFunction(executor)) {
throw new Error("Promise must accept a function as a parameter");
}
const self = this;
// 状态
self.state = PENDING;
// 值
self.result = undefined; //成功结果
self.reason = undefined; //错误原因
// 收集回调处理数组,确保回调按注册顺序执行
self.fulfilledQueues = []; //成功回调函数队列
self.rejectedQueues = []; // 失败回调函数队列
// 2.1.2 一旦状态发生改变,不可逆,并且会清空回调队列
function resolve(result) {
// 如果value是promise, 则一直展开
// 直至value为非promise为止
if (result instanceof _Promise) {
return result.then(resolve.bind(this), reject.bind(this));
}
const run = () => {
self.state = FULFILLED;
self.result = result;
// 依次执行成功队列中的函数,并清空队列
let fulfillCallback;
while ((fulfillCallback = self.fulfilledQueues.shift())) {
fulfillCallback(result);
}
};
// 为了支持同步的Promise,这里采用异步调用
if (self.state === PENDING) {
setTimeout(run, 0);
}
}
function reject(reason) {
const run = () => {
self.state = REJECTED;
self.reason = reason;
// 依次执行失败队列中的函数,并清空队列
let rejectCallBack;
while ((rejectCallBack = self.rejectedQueues.shift())) {
rejectCallBack(reason);
}
};
// 为了支持同步的Promise,这里采用异步调用
if (self.state === PENDING) {
setTimeout(run, 0);
}
}
try {
// 马上执行传入的函数,并把resolve和reject函数作为参数传进去
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// 2.2 有一个then方法,接受 onFulfilled 和 onRejected 参数
_Promise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
const { state } = self;
// 2.2.1 onFulfill和onRejected为可选参数,且为函数
// 2.2.5 两个参数必须作为函数被调用
// Promise值的穿透,当then无参数时,默认参数就是把值往后传或者抛
onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;
onRejected = isFunction(onRejected)
? onRejected
: (error) => {
throw error;
};
// 2.2.6 then可以被调用多次,因此需要维护回调队列,根据顺序执行回调
// 2.2.7 then返回一个promise
// 2.2.7.1 onFulfilled onRjected执行结果为x,调用resolvePromise
// 2.2.7.2 onFulfilled onRejected执行时抛的异常,在newPromise需reject抛出
let newPromise = new _Promise((resolve, reject) => {
if (state === PENDING) {
// 在pending时会将回调函数注册到回调队列里面,状态发生变化后再根据状态执行对应的回调
// 这里之没有异步执行,是因为这些函数必然会被resolve或reject调用,而在构造函数里resolve和reject的实现已是异步
self.fulfilledQueues.push((result) => {
try {
let x = onFulfilled(result);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
self.rejectedQueues.push((reason) => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}
// 2.2.2 onFulfilled 必须在fulfilled时调用
// 2.2.4 onFulfilled/onRejectd为微任务
if (state === FULFILLED) {
setTimeout(function() {
try {
let x = onFulfilled(result);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
// 2.2.3 onRejected 必须在rejected时调用
// 2.2.4 onFulfilled/onRejectd为微任务
if (state === REJECTED) {
setTimeout(function() {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
});
return newPromise;
};
// 2.3 resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
// 2.3.1 x为promise2本身,就抛TypeError
if (x === promise2) {
return reject(new TypeError("Chaining cycle detected for promise"));
}
// 2.3.2 x为一个新的promise
if (isPromise(x)) {
return x.then(resolve, reject);
}
// 2.3.3 x为一个对象或函数(又分thenable和非thenable)
// 2.3.3.3 如果x.then是一个函数,执行x.then.call()
// 2.3.3.4 如果 x.then不为函数,直接返回一个fulfilled的promise,result = x
// 2.3.4 x不为一个对象和函数,直接返回一个fulfilled的promise,result = x
// 整理了一下以上几点,大概简化了成以下逻辑:
// 如果为thenable,需要一个过程去将 thenable 转成 Promise,并且会立即调用 thenable 对象里面 then 的方法,重新进入resolvePromise过程(相当于额外多了一个PromiseResolveThenableJob的微任务);
// 否则 resolve(x)
// 2.3.3.1 取then函数
// 2.3.3.2 如果 x.then出错,则reject promise2 whth error
if (isThenable(x)) {
try {
const then = x.then;
then.call(
x,
function(v) {
return resolvePromise(promise2, v, resolve, reject);
},
function(error) {
return reject(reason);
}
);
} catch (error) {
return reject(error);
}
}
resolve(x);
}
# Promise 的一些 API 实现
# catch 和 finally
// then和finally一般作为链式调用,因此直接挂在prototype上面
_Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
// finally总是最后调用的,不管成功或者失败都被会调用。
_Promise.prototype.finally = function(fn) {
// 貌似对finally的行为没有一个公认的定义,所以这个实现目前是跟Q保持一致,会返回一个新的Promise而不是原来那个。
// this.then的时候已经算是最后的一个promise实例,被传递至finally函数里
return this.then(
function(v) {
setTimeout(fn);
return v;
},
function(r) {
setTimeout(fn);
throw r;
}
);
};
# resolve 和 reject
_Promise.resolve = function(val) {
var promise = new _Promise(function(resolve, reject) {
resolvePromise(promise, val, resolve, reject);
});
return promise;
};
_Promise.reject = function(reason) {
return new _Promise(function(resolve, reject) {
reject(reason);
});
};
# race
// Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise被resolve或reject,返回的 promise就会被resolve或reject。
_Promise.race = function(promises) {
return new _Promise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
_Promise.resolve(promises[i]).then(
function(val) {
return resolve(val);
},
function(reason) {
return reject(reason);
}
);
}
});
};
# all
Promise.all(iterable) 方法返回一个 promise,需要等待迭代器中的全部 promise 被 resolve,返回的 promise 才会被 resolve;否则,若中间某一个 pomise 被 reject 或者出错,则返回的新 promise 会被 reject。
_Promise.all = function(promises) {
return new _Promise(function(resolve, reject) {
const promiseNum = promises.length;
let resolvedCounter = 0;
let resolvedValues = new Array(promiseNum);
for (let i = 0; i < promiseNum i++) {
Promise.resolve(promises[i]).then(
function(value) {
resolvedCounter++;
resolvedValues[i] = value;
// 当且仅当所有promise都被resolve才会return resolve,且把resolvedValues作为结果抛出去
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues);
}
},
function(reason) {
return reject(reason);
}
);
}
});
};
# allSettled
Promise.allSettled 用法类似于 Promise.all,接受一个由 promise 异步任务组成的数组,并支持同时处理多个 promise 异步任务。
和 all 的区别就是,不管其中的 promise 元素失败还是成功,都会被 resolve,触发 then 的第一个函数,结果由包含每个 promise 的 status 和 result/reason 对象组成。
// Promise.allSettled(iterable)方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
_Promise.allSettled = function(promises) {
promises = Array.from(promises);
return new _Promise(function(resolve, reject) {
const promiseNum = promises.length;
let result = new Array(promiseNum);
let count = 0;
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promise[i]).then(
function(result) {
count++;
result[i] = { state: "fulfilled", result };
if (count === promiseNum) {
resolve(result);
}
},
function(reason) {
count++;
result[i] = { state: "rejected", reason };
if (count === promiseNum) {
resolve(result);
}
}
);
}
});
};
# 需注意
# Promise 存在值穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then((result) => console.log(result));
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。上述例子中只会打印1
。
这也体现在了 promise/A+ 规范里面的 2.2.1 中。
# then 处理错误函数 和 catch 的区别
then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。
.catch
是 .then 第二个参数
的简便写法(.cath() => .then(undefined, onRejected)
),但是它们用法上有一点需要注意:
.then 的第二个处理错误的函数
,捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。
Promise.resolve()
.then(
function success(res) {
throw new Error("error");
},
function fail1(e) {
console.error("fail1: ", e);
}
)
.catch(function fail2(e) {
console.error("fail2: ", e);
});
// fail2: Error: error at success (<anonymous>)
.then 的第二个处理错误的函数
和 catch
捕获错误信息的时候会就近原则,如果是 promise 内部报错,reject 抛出错误后,then 的第二个参数和 catch 方法都存在的情况下,只有 then 的第二个参数能捕获到,如果 then 的第二个参数不存在,则 catch 方法会捕获到。
# promise.catch(...).then(...) 和 promise.then(...).catch(...)
- promise.catch(...).then(...)
catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法。
代码运行完 catch 方法指定的回调函数,会接着运行后面那个 then 方法指定的回调函数。
如果没有报错,则会跳过 catch 方法。此时,要是最后的 then 方法里面报错,就与前面的 catch 无关了。
- promise.then(...).catch(...)
如果 promise 抛出了错误 且 then 中只提供了 onFulfilled,此时 then 会被跳过,将值一直往后传递直到 catch。
参考文章:
剖析 Promise 内部结构 (opens new window)