Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。如今前端应用中Promise已经获得了普遍使用。本文经过实现符合Promise/A+规范的Promise,对其加深印象。javascript
咱们在使用Promise时,一般是使用new操做符进行构造,传入resolver函数,该函数会接受成功(resolve)、失败(reject)的回调函数,当咱们肯定结果时,须要调用resolve或reject,具体代码以下:前端
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000)
})
// 1s后控制台打印success
p1.then(res => console.log(res))
复制代码
因此咱们的Promise也须要是个构造函数,而且执行用户传入的resolver函数,将定义好的回调函数传进去。下面是具体的代码:java
注:本文代码的实现,下划线开头表明私有属性、私有方法。git
// 定义Promise的三种状态常量
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
function Promise(resolver) {
// 传入的必须是函数
if (typeof resolver !== 'function') {
throw new TypeError('Promise resolver ' + resolver + ' is not a function');
}
// resolve或reject的结果值
this._result = undefined;
// 状态
this._status = PENDING;
try {
// 执行
resolver(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
// 捕获错误
this._reject(error);
}
}
// 私有方法,传给resolver的成功、失败回调
Promise.prototype._resolve = function() {}
Promise.prototype._reject = function() {}
复制代码
接下来咱们来实现_resolve、_reject私有方法,其实逻辑很简单,咱们只须要改变Promise状态,以及成功的值或者失败的缘由。但要注意Promise一旦状态改变,就不会再变,任什么时候候均可以获得这个结果,因此咱们只有在状态时PENDING的时才会执行。es6
Promise.prototype._resolve = function(value) {
// setTimeout 为了异步执行
setTimeout(() => {
if (this._status !== PENDING) return;
this._status = FULFILLED;
this._result = value;
});
}
Promise.prototype._reject = function (reason) {
// setTimeout 为了异步执行
setTimeout(() => {
if (this._status !== PENDING) return;
this._status = REJECTED;
this._result = reason;
});
};
复制代码
Promise的核心就是then
方法,Promise/A+规范大都也是针对then方法进行阐述,实现了then
方法后,咱们再来实现其余API就方便了不少。github
Promise的then
方法的规范有以下几点编程
接受两个参数,onFulfilled(成功回调), onRejected(失败回调),当回调不是函数时, 其必须被忽略,支持透传promise
then 方法能够被同一个 Promise 调用屡次异步
then 方法必须返回一个 Promise 对象异步编程
咱们针对上述几点分别来实现一下
关于第一点,能够控制台执行下面代码
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000)
})
// 1s后控制台打印success
p1.then(1).then(res => console.log(res))
复制代码
第二个then方法依然能够接受到resolve成功的值,因此当then方法传入的不是函数时,咱们要规范使其变成函数支持透传。
Promise.prototype.then = function (onFulfilled, onRejected) {
// 保证是函数,不是函数要实现透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected = typeof onRejected === 'function' ? onRejected : (v) => { throw v };
}
复制代码
实现第一点很简单,咱们只须要吧把结果/错误 -> 返回/抛出传递给出去就能够啦。
咱们再来看第二点,为了能够屡次调用而且依次执行,咱们须要改下以前写过的代码,咱们须要增长俩个回调队列,成功、失败各一个。其实也能够用一个队列来存储,我这里采用的分别存储。
function Promise(resolver) {
// 忽略无关代码...
// resolve的回调队列
+ this._resolveCbs = [];
// reject的回调队列
+ this._rejectCbs = [];
}
Promise.prototype._resolve = function(value) {
setTimeout(() => {
// 忽略无关代码...
+ this._resolveCbs.forEach((callback) => callback(value));
});
}
Promise.prototype._reject = function(reason) {
setTimeout(() => {
// 忽略无关代码...
+ this._rejectCbs.forEach((callback) => callback(reason));
});
}
复制代码
咱们在then方法中,若是状态还处于PENDING,就须要将传入的onFulfilled(成功回调), onRejected(失败回调)插入对应的队列中,不然直接执行就好。这也就是咱们要实现第三点的核心逻辑。
根据第三点所述,咱们老是须要执行onFulfilled 或 onRejected,而后传入Promise解决过程,此外还须要捕获这个过程,直接reject。具体核心代码以下
let promise = undefined;
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onFulfilled(this._result); // 或者 var x = onRejected(this._result);
// resolvePromise Promise解决过程 下一段讲
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
复制代码
下面咱们就是把这段代码分别用在 PENDING, FULFILLED, REJECTED三种状态,完整代码以下:
Promise.prototype.then = function (onFulfilled, onRejected) {
// 保证是函数,不是函数要实现透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (v) => {
throw v;
};
let promise = undefined;
// 已经resolve
if (this._status === FULFILLED) {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onFulfilled(this._result);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
// 已经reject
if (this._status === REJECTED) {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onRejected(this._result);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
// pending时直接放入回调队列中,放入队列汇总不须要加setTimeout,由于执行时候已是setTimeout中
if (this._status === PENDING) {
return (promise = new Promise((resolve, reject) => {
this._resolveCbs.push((value) => {
try {
var x = onFulfilled(value);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
this._rejectCbs.push((reason) => {
try {
var x = onRejected(reason);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
};
复制代码
上面代码看似不少,很复杂,但其实根据规范来看,其实很简单,并且有大量的重复代码。那咱们还有一个resolvePromise
函数没有完成,接下来我但愿读者能够本身去读一下Promise 解决过程的逻辑。点击连接去查看。由于函数的实现彻底照规范的逻辑书写,没有技巧可言。
这里简单的总结几点:
为了和其余promise并存,咱们不能只判断onFulfilled,onRejected函数返回的是不是promise,咱们只须要保证其返回值存在then
方法就去尝试按promise处理。
若是没有then属性,或者then属性不是函数的话,直接按照resolve(x)处理
若是then存在而且是函数,按照promise处理的同时,须要捕获错误按reject(x)处理
若是传入的回调均被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用(只能调用一次,须要有标志位)
按照上述四点,咱们能够写出resolvePromise
函数的代码
function resolvePromise(promise, x, resolve, reject) {
// 若是 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}
// 用于 “优先采用首次调用并忽略剩下的调用”的标志位
let invoked = false;
// 尝试把 x.then 赋值给 then
let then = undefined;
// x 为对象或函数
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
then = x.then;
if (typeof then === 'function') {
// 若是 then 是函数,将 x 做为函数的做用域 this 调用之
then.call(
x,
(y) => {
if (invoked) return;
invoked = true;
return resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (invoked) return;
invoked = true;
return reject(r);
}
);
} else {
// 若是 then 不是函数,以 x 为参数执行 promise
return resolve(x);
}
} catch (e) {
// 若是取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
if (invoked) return;
invoked = true;
return reject(e);
}
} else {
// 若是 x 不为对象或者函数,以 x 为参数执行 promise
return resolve(x);
}
}
复制代码
.catch()发生错误时的回调函数 至关于使用.then(null, onRejected),咱们实现了then方法,因此catch方法就至关简单
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
复制代码
.finally()方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码
上面代码中,无论promise最后的状态,在执行完then或catch指定的回调函数之后,都会执行finally方法指定的回调函数。
那咱们就能够经过then方法,传入的成功、失败回调函数中都去执行callback
Promise.prototype.finally = function (callback) {
return this.then(
(value) => Promise.resolve(callback()).then(() => value),
(reason) =>
Promise.resolve(callback()).then(() => {
throw reason;
})
);
};
复制代码
注意咱们要.then将结果透传,由于finally后面还能够继续调用then方法。
// 最后一个then理应接受到2做为参数
Promise.resolve(2).finally(() => { }).then(res => console.log(res))
复制代码
有时须要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个做用。
咱们只须要把then方法中的成功逻辑拿出来使用就能够。(其中x不是onFulfilled执行的值,直接是传入的参数)
Promise.resolve = function (value) {
let promise;
return (promise = new Promise((resolve, reject) => {
resolvePromise(promise, value, resolve, reject);
}));
};
复制代码
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
Promise.reject = function (reason) {
return new Promise((_, reject) => reject(reason));
};
复制代码
/** * Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 * 只有全部实例的状态都变成fulfilled,新的实例状态才会变成fulfilled,新实例参数 * 只要其中有一个被rejected,新的实例就变成rejected,此时第一个被reject的实例的返回值,会传递给新实例的回调函数。 */
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let resolvedCount = 0;
let promiseCount = promises.length;
let resolvedValues = new Array(promiseCount);
for (let i = 0; i < promiseCount; i++) {
Promise.resolve(promises[i]).then(
(value) => {
resolvedCount++;
resolvedValues[i] = value;
// 数量相同说明promise实例都是成功
if (resolvedCount == promiseCount) {
return resolve(resolvedValues);
}
},
(reason) => {
// 率先reject的直接失败,传入缘由
return reject(reason);
}
);
}
});
};
复制代码
/** * Promise.race()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 * 只要其中有一个状态变动,新的实例就跟随着变动,参数会传递给新实例的回调函数。 */
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
// 谁快谁说了算!
Promise.resolve(promises[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
}
);
}
});
};
复制代码
Promise的实现难点主要集中在then方法上,其余方法都是基于then方法实现的。实现一个Promise也是笔试的高频题目,但愿本文章能够给你带来帮助。