Promise修炼秘籍
Promise理论
在手写Promise之前,我们应该先对Promise的每个方法都有一个最基本的认识。
- Promise.resolve(value):创建一个已经被解决 (非pending)(可能成功,可能失败)的Promise对象,返回值为value。
- Promise.reject(reason):创建一个已经失败的Promise对象,返回值为reason。
- Promise.all(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在所有Promise对象都解决后才会解决。如果其中一个Promise对象失败了,该Promise对象将立即失败。
- Promise.race(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在第一个Promise对象解决后就会解决。如果第一个Promise对象失败了,该Promise对象将立即失败。
- Promise.prototype.then(onFulfilled, onRejected):为Promise对象添加解决(onFulfilled)和拒绝(onRejected)回调函数,并返回一个新的Promise对象。
- Promise.prototype.catch(onRejected):为Promise对象添加拒绝回调函数,并返回一个新的Promise对象。
- Promise.prototype.finally(onFinally):为Promise对象添加一个回调函数,该回调函数在Promise对象解决或拒绝后都会执行,并返回一个新的Promise对象。
- Promise.allSettled(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在所有Promise对象都解决或拒绝后才会解决。该方法返回一个数组,数组中包含每个Promise对象的解决或拒绝结果。
- Promise.any(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在至少有一个Promise对象解决后就会解决。如果所有Promise对象都拒绝了,该Promise对象将立即拒绝。该方法返回第一个解决的Promise对象的值。
- deferred,是一种基于Promise的编程模式。它是一种将Promise的解决和拒绝函数分离的方法,使得在编写异步代码时更加简单和易于理解。
Promise雏形
Promise,3种状态,pending(等待中),fulfilled(成功|解决),rejected(失败|拒绝)。
PromiseResult
是Promise 被解决或拒绝时 的结果值。
在构造函数中,我们将其初始化为null
,表示Promise还没有被解决或拒绝。
当Promise被解决时,我们将其值设为解决值,当Promise被拒绝时,我们将其值设为拒绝原因。
onFulfilledCallbacks,onRejectedCallbacks
这两个变量分别是成功解决和被拒绝时的回调函数列表。当Promise被解决或拒绝时,这些回调函数将会被调用。
Promise构造函数接收一个函数作为参数,该函数被称为执行器函数(executor function)。
执行器函数有两个参数,分别是resolve
和reject
函数,用于将Promise解决或拒绝。
const promise = new Promise((resolve, reject) => {
const randomNumber = Math.random();
if (randomNumber > 0.5) {
resolve('Success');
} else {
reject('Failure');
}
});
promise.then(
result => console.log(result), // Success
error => console.error(error) // Failure
);
bind
在JavaScript中,函数的执行上下文由其调用方式决定。当我们将一个函数作为参数传递给另一个函数时,该函数的执行上下文可能会发生改变,导致this
关键字指向错误的对象。为了避免这种情况,我们可以使用bind
方法将函数绑定到指定的上下文中,以确保在函数执行时this
关键字指向正确的对象。
const person = {
name: 'Alice',
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
};
setTimeout(person.sayHello.bind(person), 1000);
在Promise构造函数中,我们需要把bind
方法将resolve
和reject
函数绑定到Promise实例上。
这样,当我们在执行器函数中调用resolve
和reject
函数时,它们会在Promise实例的上下文中执行,而不是在全局上下文中执行。这样可以确保this
关键字指向正确的对象,从而避免意外的错误。
至此,Promise 最基础的代码结构就可以写好了。
Promise初始版
class myPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(func) {
this.PromiseState = myPromise.PENDING;
this.PromiseResult = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
try {
func(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(result) {
if (this.PromiseState === myPromise.PENDING) {
this.PromiseState = myPromise.FULFILLED;
this.PromiseResult = result;
this.onFulfilledCallbacks.forEach(callback => {
callback(result)
})
}
}
reject(reason) {
if (this.PromiseState === myPromise.PENDING) {
this.PromiseState = myPromise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallbacks.forEach(callback => {
callback(reason)
})
}
}
}
Promise完善
下面,我们开始逐步实现文章开头的10个方法。
首先,我们先明确 Promise.resolve 这个方法的功能是创建一个已解决的Promise对象,可能因为中英文语言的差异,fulfilled 翻译过来是 成功 | 解决,
所以 很多萌新会认为 Promise.resolve 必定 返回 一个 fulfilled 状态 的 Promise,其实不对,而应该是 返回 一个 非 pending 状态。
其实,中文环境下的 已解决 好像也不一定是成功。比如,购物结束付款这个事,已解决,只是说明我结束了购物结束付款这个事,不一定代表我付款成功了。(无不良引导!)
Promise.resolve
下面说个Promise的冷知识。因为它相对不太常见,而且在实践中很少使用。但是,它对于理解Promise的工作原理和实现方式非常重要。
在Promise规范中,如果一个值是thenable,即它具有then
方法,那么这个值可以被视为一个Promise对象。在这种情况下,返回的Promise对象会“跟随”thenable对象,其状态和结果将会与thenable对象的状态和结果保持一致。
值得一谈的是,Promise.resolve(new Error('123')) 返回的是一个状态为fulfilled的Promise。运行以下代码,就可以领悟其中的巧妙之处。
Promise.resolve().then(()=>{
throw new Error('123');
}).catch((reason)=>{
console.log('catch',reason);
})
Promise.resolve().then(() => {
return new Error('456');
}).then((value)=>{
console.log('then',value);
})
static resolve(value) {
// 如果这个值是一个 promise ,那么将返回这个 promise
if (value instanceof Promise) {
return value;
} else if (value instanceof Object && 'then' in value) {
// 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
return new Promise((resolve, reject) => {
value.then(resolve, reject);
})
}
// 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
return new Promise((resolve) => {
resolve(value)
})
}
当你会写Promise.resolve 后,Promise.reject 有手就行。
Promise.reject
返回一个失败的Promise, 入参为 失败的原因 reason
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
仔细对比下,Promise.prototype.then(),Promise.prototype.catch(),Promise.prototype.finally() 这 三个方法功能,其实本质都是一样的,给某一种或两种 非pending状态绑定回调函数,这也是 手写Promise 最 精华(困难)的地方。
Promise.prototype.catch()
结果失败 rejected,执行回调函数
catch (onRejected) {
return this.then(undefined, onRejected)
}
Promise.prototype.finally()
无论结果是fulfilled或者是rejected,执行回调函数
finally(callBack) {
return this.then(callBack, callBack)
}
Promise.prototype.then()
因为then方法中可以传 两个回调函数(虽然我一般只传第一个,另一个用catch代替)分别代表 成功、失败。
需要 根据 PromiseState 状态,走不同的代码逻辑。
如果处于pending状态,就加 成功的回调 push 到 onFulfilledCallbacks,失败的回调 push 到 onRejectedCallbacks。
如果 传入的参数 不是函数的话,直接 返回 promise。
Promise的源码中使用微任务来模拟异步操作,我们手写可以用setTimeout达到异步效果。
then(onFulfilled, onRejected) {
let promise2 = new myPromise((resolve, reject) => {
if (this.PromiseState === myPromise.FULFILLED) {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
} else if (this.PromiseState === myPromise.REJECTED) {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e)
}
});
} else if (this.PromiseState === myPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
});
}
})
return promise2
}
function resolvePromise(promise2, x, resolve, reject) {
// 回调函数 返回 的 promise是 自身, 死循环, 异常
if (x === promise2) {
throw new TypeError('Chaining cycle detected for promise');
}
if (x instanceof myPromise) {
// 回调结果还是 promise 类型,则 继续 .then ,promise链式调用的原理
// reject对应的回调函数 直接传下去 ,catch 方法透传的原理。
x.then(y => {
resolvePromise(promise2, y, resolve, reject)
}, reject);
} else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
try {
var then = x.then;
} catch (e) {
return reject(e);
}
//因此,这段代码的作用就是处理 thenable 对象,将它们转换为 Promise 对象,并根据 then 方法的返回值来确定返回的 Promise 对象的状态和值。
//这样就可以让 thenable 对象也能够被 Promise.prototype.then 方法处理,并进行链式调用。
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
)
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
} else {
return resolve(x);
}
}
至此,手写 Promise 只剩下静态方法部分,其实我认为这些反而简单,因为每一个都是相对独立的,记住功能就能写,都不用记。
Promise.all
成功按顺序拿全部,失败只要头一个失败,局部失败等于整体失败。
static all(promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
let result = [];
let count = 0;
if (promises.length === 0) {
return resolve(promises);
}
promises.forEach((item, index) => {
Promise.resolve(item).then(
value => {
count++;
result[index] = value;
count === promises.length && resolve(result);
},
reason => {
reject(reason);
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
Promise.race
比谁快,谁快就是谁,成功失败无所谓
static race(promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
if (promises.length > 0) {
promises.forEach(item => {
Promise.resolve(item).then(resolve, reject);
})
}
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
Promise.allSettled
不管成功与否,都把结果完整带来
static allSettled(promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
let result = [];
let count = 0;
if (promises.length === 0) return resolve(promises);
promises.forEach((item, index) => {
Promise.resolve(item).then(
value => {
count++;
result[index] = {
status: 'fulfilled',
value
}
count === promises.length && resolve(result);
},
reason => {
count++;
result[index] = {
status: 'rejected',
reason
}
count === promises.length && resolve(result);
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
Promise.any
只会记住第一个成功的人,如果全都失败就得记录下所有人的失败原因了。
static any(promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
let errors = [];
let count = 0;
if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));
promises.forEach(item => {
Promise.resolve(item).then(
value => {
resolve(value);
},
reason => {
count++;
errors.push(reason);
count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'));
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
Promise.deferred
Promise.deferred = function () {
let result = {};
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
至此,一个几乎完美的Promise就完成了。说实话,我觉得 thenable 可以不写,知道有这回事就行。
Promise面试手写版
手写 Promise 的主要目的是为了更好地理解 Promise 的工作原理,以及更好地掌握 Promise 的使用技巧,而不是为了完全实现 Promise 的所有功能。
class myPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(func) {
this.PromiseState = myPromise.PENDING;
this.PromiseResult = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
try {
func(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(result) {
if (this.PromiseState === myPromise.PENDING) {
this.PromiseState = myPromise.FULFILLED;
this.PromiseResult = result;
this.onFulfilledCallbacks.forEach(callback => {
callback(result)
})
}
}
reject(reason) {
if (this.PromiseState === myPromise.PENDING) {
this.PromiseState = myPromise.REJECTED;
this.PromiseResult = reason;
this.onRejectedCallbacks.forEach(callback => {
callback(reason)
})
}
}
then(onFulfilled, onRejected) {
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
throw new TypeError('Chaining cycle detected for promise');
}
if (x instanceof myPromise) {
x.then(y => {
resolvePromise(promise2, y, resolve, reject)
}, reject);
} else{
resolve(x);
}
}
let promise2 = new myPromise((resolve, reject) => {
if (this.PromiseState === myPromise.FULFILLED) {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
} else if (this.PromiseState === myPromise.REJECTED) {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e)
}
});
} else if (this.PromiseState === myPromise.PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled !== 'function') {
resolve(this.PromiseResult);
} else {
let x = onFulfilled(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected !== 'function') {
reject(this.PromiseResult);
} else {
let x = onRejected(this.PromiseResult);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
});
});
}
})
return promise2
}
static resolve(value) {
if (value instanceof myPromise) {
return value;
} else{
return new myPromise((resolve) => {
resolve(value)
})
}
}
static reject(reason) {
return new myPromise((resolve, reject) => {
reject(reason);
})
}
catch (onRejected) {
return this.then(undefined, onRejected)
}
finally(callBack) {
return this.then(callBack, callBack)
}
static all(promises) {
return new myPromise((resolve, reject) => {
if (Array.isArray(promises)) {
let result = []; // 存储结果
let count = 0; // 计数器
if (promises.length === 0) {
return resolve(promises);
}
promises.forEach((item, index) => {
myPromise.resolve(item).then(
value => {
count++;
result[index] = value;
count === promises.length && resolve(result);
},
reason => {
reject(reason);
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
static allSettled(promises) {
return new myPromise((resolve, reject) => {
if (Array.isArray(promises)) {
let result = []; // 存储结果
let count = 0; // 计数器
if (promises.length === 0) return resolve(promises);
promises.forEach((item, index) => {
myPromise.resolve(item).then(
value => {
count++;
result[index] = {
status: 'fulfilled',
value
}
count === promises.length && resolve(result);
},
reason => {
count++;
result[index] = {
status: 'rejected',
reason
}
count === promises.length && resolve(result);
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
static any(promises) {
return new myPromise((resolve, reject) => {
if (Array.isArray(promises)) {
let errors = []; //
let count = 0; // 计数器
if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));
promises.forEach(item => {
myPromise.resolve(item).then(
value => {
resolve(value);
},
reason => {
count++;
errors.push(reason);
count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'));
}
)
})
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
static race(promises) {
return new myPromise((resolve, reject) => {
if (Array.isArray(promises)) {
if (promises.length > 0) {
promises.forEach(item => {
myPromise.resolve(item).then(resolve, reject);
})
}
} else {
return reject(new TypeError('Argument is not iterable'))
}
})
}
}
myPromise.deferred = function () {
let result = {};
result.promise = new myPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
参考文章
看不懂的地方,建议看这个,膜拜大佬。 juejin.cn/post/704375…