前言
Promise 作为ES6新增的重头戏,给前端开发者们带来了极大的便利。个人觉得呢,掌握一个知识点最好的方式,就是运用之前的知识去实现它,不能实现的,则多实践,多运用,Promise则完全可以用es5之前的知识支实现的。手写Promsie不仅可以更加熟练的使用Promise,还可以大大加深对回调函数的运用。在es6的Promise出现之前,社区里面出现了一些Promse规范,如Promise A+等等。下面则按照es6的标准,使用es5的知识实现一个完整的Promise
核心方法then
Promise的核心方法是then方法,其它方法均可以通过then方法实现,先把then方法的基础功能实现,再解决其中的一些问题、完善其它的功能,再利用then方法实现其它方法即可
架构搭建
定义好Promise函数、相关的状态值、原型方法和静态方法等。后面的代码只贴在前一部分上修改的部分
// 定义 promise 类
function MyPromise() {}
// 使用属性描述符定义几种状态,防止被修改
Object.defineProperties(MyPromise, {
STATUS_PENDING: {
value: 'pending',
},
STATUS_FULFILLED: {
value: 'fulfilled',
},
STATUS_REJECTED: {
value: 'rejected',
},
});
// 原型方法
MyPromise.prototype = {
then: function () {},
};
// 修正构造器
Object.defineProperty(MyPromise.prototype, 'constructor', {
value: MyPromise,
});
// all、allSettled等静态方法
var staticMethods = {
all: function () {},
};
for (const mKey in staticMethods) {
if (!MyPromise.hasOwnProperty(mKey)) {
Object.defineProperty(MyPromise, mKey, { value: staticMethods[mKey] });
}
}
then方法基础功能实现
实现then方法的基础功能:
- 当调用
resolve时,promise进入fulfilled状态,调用then方法传入的第1个回调 - 当调用
reject时,promise进入rejected状态,调用then方法传入的第2个回调 - 执行创建
Promise时传入的回调如果出现异常,promise进入rejected状态 - 同一个实例可以多次调用
then方法,并且可以正常执行回调
function MyPromise(executor) {
var self = this
this._status = MyPromise.STATUS_PENDING
this._onFulfilledFns = [] //用于存放当 promise 状态变为 fulfilled 时的回调函数
this._onRejectedFns = []
// 产生异常时,进入 rejected 状态
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
function resolve(result) {
// 异步执行 fulfilled 回调
setTimeout(function() {
// 校验状态,防止多次执行
if (self._status !== MyPromise.STATUS_PENDING) {
return
}
self._status = MyPromise.STATUS_FULFILLED
self._onFulfilledFns.forEach(function (fn) {
fn(result)
})
})
}
function reject(reason) {
setTimeout(function() {
if (self._status !== MyPromise.STATUS_PENDING) {
return
}
self._status = MyPromise.STATUS_REJECTED
self._onRejectedFns.forEach(function (fn) {
fn(reason)
})
})
}
}
// ...
MyPromise.prototype = {
then: function (onResolved, onRejected) {
if (typeof onResolved === 'function') {
this._onFulfilledFns.push(onResolved)
}
if (typeof onRejected === 'function') {
this._onRejectedFns.push(onRejected)
}
},
};
上面的代码实现了基础功能,但是同时还还在以下问题:
- 不支持链式调用
- 不支持状态已确定的
promise:当调用then方法时,如果promise状态已经发生改变,那么将不会执行回调
支持链式调用、状态确定的promise
想让 then 方法支持链式调用,那么肯定要返回一个 promise 对象;而要支持状态确定的 promise,我们需要在 promise状态发生改变时,保存 promise 的值,并且在调用 then 方法时,直接执行回调函数
function MyPromise(executor) {
// ...
function resolve(result) {
// 异步执行回调
setTimeout(function() {
if (self._status !== MyPromise.STATUS_PENDING) {
return
}
self._status = MyPromise.STATUS_FULFILLED
self._value = result // 保存 promise 的结果值
self._onFulfilledFns.forEach(function (fn) {
fn(result)
})
})
}
function reject(reason) {
setTimeout(function() {
if (self._status !== MyPromise.STATUS_PENDING) {
return
}
self._status = MyPromise.STATUS_REJECTED
self._value = reason
self._onRejectedFns.forEach(function (fn) {
fn(reason)
})
})
}
}
// 状态发生变化时的中转站,由此调用 resolve 和 reject 方法
function stateChangedMiddleware(onStateChanged, value, resolve, reject) {
try {
var result = onStateChanged(value)
resolve(result)
} catch (err) {
reject(err)
}
}
// 原型方法
MyPromise.prototype = {
then: function (onResolved, onRejected) {
var self = this
// 根据es6 Promise 的功能,当回调执行异常时此 promise 进行 rejected 状态,否则进入 resolved 状态
return new MyPromise(function (resolve, reject) {
if (typeof onResolved === 'function') {
// promise 状态已经发生改变的话,异步执行
if (self._status === MyPromise.STATUS_FULFILLED) {
setTimeout(function() {
stateChangedMiddleware(onResolved, self._value, resolve, reject)
})
} else {
//因为我们要拿到执行结果并传递给 resolve,所以在这里套一层函数
self._onFulfilledFns.push(function(result) {
stateChangedMiddleware(onResolved, result, resolve, reject)
})
}
}
if (typeof onRejected === 'function') {
if (self._status === MyPromise.STATUS_REJECTED) {
setTimeout(function() {
stateChangedMiddleware(onRejected, self._value, resolve, reject)
})
} else {
self._onRejectedFns.push(function(reason) {
stateChangedMiddleware(onRejected, reason, resolve, reject)
})
}
}
})
},
};
到此,then 方法的核心功能已经实现了,但是相较于 es6 Promise 的 resolve 回调,它还不支持 thenable 对象
支持 thenable 对象
thenable 对象
所谓 thenable 对象,就是我们在调用 promise 对象的 resolve 回调时,如果传入的是一个带有 then 属性、并且这个属性是一个方法时,那么 promise 会将 resolve和 reject 作为参数传入该方法,并且该 promise 的状态也由 then 方法决定。比如在下面这段代码中,输出的是thenable fulfilled而不是带有 then 方法的对象:
new Promise(resolve => {
resolve({
then(innerResolve) {
innerResolve('thenable fulfilled')
}
})
}).then(res => console.log(res)) // 'thenable fulfilled'
要支持此功能,只需要判断一下 resolve 传入的参数即可:
function resolve(result) {
if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
// 异步调用
setTimeout(function () {
result.then(resolve, reject)
})
return
}
// ...
}
catch 和 finally
catch方法
在es6的promise 中,可以使用 then 方法的第二个传入 rejected 时的函数,也可以通过catch方法传入。所以理论上,我们只需要将catch 的参数 传入then 方法的第二个参数即可:
MyPromise.prototype = {
// ...
catch: function (onRejected) {
return this.then(null, onRejected)
}
};
但是我们在实现 then 方法的时候,并没有处理传入的参数不是函数的情况,导致在参数不为函数时,当前 promise 的状态和结果不可以传递到下一个 promise,所以这种情况下,我们要传入一个函数,将 promise 的结果传递下去:
MyPromise.prototype = {
then: function (onResolved, onRejected) {
var self = this
return new MyPromise(function (resolve, reject) {
if (!(typeof onResolved === 'function')) {
onResolved = function (res) { return res }
}
// ...
if (!(typeof onRejected === 'function')) {
onRejected = function (reason) { throw reason }
}
// ...
})
},
catch: function (onRejected) {
return this.then(null, onRejected)
}
};
finally 方法
类似的,只需要将 finally 方法的参数传递给 then 方法的两个参数即可实现 finally 方法:
MyPromise.prototype = {
// ...
finally: function (onStateChanged) {
return this.then(onStateChanged, onStateChanged)
}
};
静态方法
resolve 和 reject
同样通过 then 方法返回一个 fulfilled 或 rejected 状态的 promise 即可:
var staticMethods = {
resolve: function (p) {
return new MyPromise(function (resolve) {
resolve(p)
})
},
reject: function (p) {
return new MyPromise(function (resolve, reject) {
reject(p)
})
}
};
all等其它静态方法
all、allSettled、any 和 race等均返回一个 promise 对象,其状态和结果根据传入的参数决定。只要我们在合适的条件下调用 resolve 和 reject 回调即可
全部代码
以下代码尽量按照es6 Promise 的功能实现,但是没有保证所有场景均和 es6 Promise 表现一致,如 es6 Promise.all 方法支持可迭代对象,但是这里只支持数组。
// 定义 promise 类
function MyPromise(executor) {
var self = this;
this._status = MyPromise.STATUS_PENDING;
this._onFulfilledFns = []; //用于存放当 promise 状态变为 fulfilled 时的回调函数
this._onRejectedFns = [];
// 产生异常时,进入 rejected 状态
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
function resolve(result) {
if (isThenable(result)) {
// 异步调用
setTimeout(function () {
result.then(resolve, reject);
});
return;
}
// 校验状态,防止多次执行
// 异步执行回调
setTimeout(function () {
if (self._status !== MyPromise.STATUS_PENDING) {
return;
}
self._status = MyPromise.STATUS_FULFILLED;
self._value = result; // 保存 promise 的结果值
self._onFulfilledFns.forEach(function (fn) {
fn(result);
});
});
}
function reject(reason) {
setTimeout(function () {
if (self._status !== MyPromise.STATUS_PENDING) {
return;
}
self._status = MyPromise.STATUS_REJECTED;
self._value = reason;
self._onRejectedFns.forEach(function (fn) {
fn(reason);
});
});
}
}
// 使用属性描述符定义几种状态,防止被修改
Object.defineProperties(MyPromise, {
STATUS_PENDING: {
value: 'pending',
},
STATUS_FULFILLED: {
value: 'fulfilled',
},
STATUS_REJECTED: {
value: 'rejected',
},
});
// 状态发生变化时的中转站,由此调用 resolve 和 reject 方法
function stateChangedMiddleware(onStateChanged, value, resolve, reject) {
var result;
try {
result = onStateChanged(value);
resolve(result);
} catch (err) {
reject(err);
}
// 为了 any 方法的错误可以抛出
if (result instanceof AggregateError) {
throw result;
}
}
// 原型方法
MyPromise.prototype = {
then: function (onResolved, onRejected) {
var self = this;
// 根据es6 Promise 的功能,当回调执行异常时此 promise 进行 rejected 状态,否则进入 resolved 状态
return new MyPromise(function (resolve, reject) {
if (!(typeof onResolved === 'function')) {
onResolved = function (res) {
return res;
};
}
// promise 状态已经发生改变的话,异步执行
if (self._status === MyPromise.STATUS_FULFILLED) {
setTimeout(function () {
stateChangedMiddleware(onResolved, self._value, resolve, reject);
});
} else {
//因为我们要拿到执行结果并传递给 resolve,所以在这里套一层函数
self._onFulfilledFns.push(function (result) {
stateChangedMiddleware(onResolved, result, resolve, reject);
});
}
if (!(typeof onRejected === 'function')) {
onRejected = function (reason) {
throw reason;
};
}
if (self._status === MyPromise.STATUS_REJECTED) {
setTimeout(function () {
stateChangedMiddleware(onRejected, self._value, resolve, reject);
});
} else {
self._onRejectedFns.push(function (reason) {
stateChangedMiddleware(onRejected, reason, resolve, reject);
});
}
});
},
catch: function (onRejected) {
return this.then(null, onRejected);
},
finally: function (onStateChanged) {
return this.then(onStateChanged, onStateChanged);
},
};
// 修正构造器
Object.defineProperty(MyPromise.prototype, 'constructor', {
value: MyPromise,
});
function isThenable(o) {
return o !== null && typeof o === 'object' && typeof o.then === 'function';
}
function formatePms(pms) {
if (Object.prototype.toString.call(pms) !== '[object Array]') {
throw new TypeError('excepted array');
}
pms.forEach(function (pm, i) {
if (!isThenable(pm)) {
pm = MyPromise.resolve(pm);
}
});
}
// all、allSettled等静态方法
var staticMethods = {
resolve: function (p) {
return new MyPromise(function (resolve) {
resolve(p);
});
},
reject: function (p) {
return new MyPromise(function (resolve, reject) {
reject(p);
});
},
// es6 promise 下列静方法的参数是可迭代对象
all: function (pms) {
var count = 0,
results = [];
return new MyPromise(function (resolve, reject) {
formatePms(pms);
pms.forEach(function (pm, i) {
pm.then(function (result) {
results[i] = result;
if (++count === pms.length) {
resolve(results);
}
}).catch(function (reason) {
reject(reason);
});
});
});
},
allSettled: function (pms) {
var count = 0,
results = [];
return new MyPromise(function (resolve) {
formatePms(pms);
pms.forEach(function (pm, i) {
pm.then(function (result) {
results[i] = { status: MyPromise.STATUS_FULFILLED, value: result };
if (++count === pms.length) {
resolve(results);
}
}).catch(function (reason) {
results[i] = { status: MyPromise.STATUS_REJECTED, reason: reason };
if (++count === pms.length) {
resolve(results);
}
});
});
});
},
any: function (pms) {
var count = 0;
return new MyPromise(function (resolve) {
formatePms(pms);
pms.forEach(function (pm) {
pm.then(function (result) {
resolve(result);
}).catch(function () {
if (++count === pms.length) {
return new AggregateError('All promises were rejected');
}
});
});
});
},
race: function (pms) {
return new MyPromise(function (resolve, reject) {
formatePms(pms);
pms.forEach(function (pm) {
pm.then(function (result) {
resolve(result);
}).catch(function (reason) {
reject(reason);
});
});
});
},
};
for (const mKey in staticMethods) {
if (!MyPromise.hasOwnProperty(mKey)) {
Object.defineProperty(MyPromise, mKey, { value: staticMethods[mKey] });
}
}