实现Promise所有核心功能和方法
一直以来对Promise只是会用简单的方法,例如then,catch等,对于其余各种方法也只是简单了解,这次想要通过实现Promise来加深对Promise的使用
话不多说,直接开始,简单粗暴一步步来
一:了解Promise
1.1 什么是Promise
Promise是一种用于处理异步操作的JS对象,可以通过链式调用的方式来处理操作的结果
使Promise可以更加优雅地处理异步代码,避免回调地狱的产生
1.2 Promise的三种状态
-
Pending(进行中):初始状态,表示操作尚未完成 -
Fulfilled(已完成):表示操作成功完成 -
Rejected(已拒绝):表示操作失败
1.3 示例
通过示例来了解Promise的基本用法
const p = new Promise((resolve, reject) =>
{
resolve(1)
})
const p2 = p.then(res =>
{
console.log(res) // 1
throw 'xxx'
})
p2.catch(error =>
{
console.log(error); //xxx
})
-
通过 new Promise创建一个Promise实例对象,在构造函数中传入一个执行器函数,该函数接收两个参数-
resolve函数:如果操作成功,调用resolve函数并传递结果 -
reject函数:如果调用失败,或抛出错误,调用reject函数并传递错误信息
-
-
通过Promise的实例对象上的 then方法,可以指定操作成功时所要执行的回调函数 -
通过 catch方法,可以指定操作失败时候要执行的回调函数-
在上述代码中,调用resolve函数,更改为成功状态并传递结果1 -
通过调用p实例上的then方法来接收传递的结果,res接收到的结果即是1,并自定义回调函数,在回调函数中抛出错误信息'xxx',并使用p2来接收p.then的返回值 -
p2实际也是一个Promise实例,因为promise支持链式调用,返回值也是一个promise对象,那么p.then中抛出的错误会被p2实例身上的catch方法捕获 -
最终通过 catch方法打印对应的错误信息
-
状态变化
-
执行器函数执行 resolve(1),会将p实例对象的状态由Pending转为Fulfilled状态,即由待定状态转为成功状态 -
在p实例的then回调函数中,抛出错误信息xxx,由于异常未被捕获,p2对象状态被设置为 Rejected,p2实例的catch方法会根据此时p2实例的状态捕获错误信息xxx
then方法
-
实际上,Promise对象的 then方法会根据实例的不同状态执行不同的处理方法 -
then方法接收两个可选参数, onFulfied和onRejected,分别表示成功时和失败时的回调函数-
当Promise对象的状态为Fulfilled(已完成)时, then方法会立即执行onFulfilled回调函数,并将操作结果作为参数传递给该函数。 -
当Promise对象的状态为Rejected(已拒绝)时, then方法会立即执行onRejected回调函数,并将拒绝原因(错误信息)作为参数传递给该函数。
-
const p = new Promise((resolve, reject) =>
{
resolve(1)
// reject('xxx')
})
p.then(res =>
{
console.log(res) // 1
}, error =>
{
console.log(error);
})
-
该代码,如果执行resolve函数,状态会被改为Fulfilled(已完成),那么就会执行then方法中的第一个回调函数,并将1传递给res -
如果执行reject函数,状态会被改为Rejected(已拒绝),执行第二个回调函数,并将xxx传递给error -
在之前的代码中,catch方法的功能和执行 onRejected回调函数(第二个参数的回调函数)是一致的,都是用于当实例对象为Rejected状态时候,执行对应的回调函数,并将错误信息作为参数传递给该函数-
在MDN中有说明,此方法是 Promise.prototype.then(undefined, onRejected)的一种简写形式
-
二:实现Promise核心功能
了解Promise的基本使用过后,那么就可以来一步步去实现一个Promise,先去实现Promise的核心功能*
-
「构造函数」:通过class创建 MyPromise类,接收执行器函数(resolve/reject)并执行 -
「更改状态和原因」:通过resolve和reject函数去更改实例对象的状态,并将原因(操作结果)作为参数传递 -
「初步实现then方法」:添加实例then方法,并判断传入then方法的参数类型,设置成功和失败的回调函数 -
「支持异步操作和多次调用」:设置数组存储回调函数来实现多次通过实例调用then方法(非链式调用),通过设置异步函数来处理异步调用resolve/reject -
「then的异步执行」 -
「then的链式调用」:返回MyPromise实例,异常处理,获取then的返回值,对于不同的返回值做出不同处理
2.1 构造函数
class MyPromise
{
// 1. 添加构造函数
constructor(func)
{
// 2. 定义resolve/reject
const resolve = (result) =>
{
console.log(result) //success
}
const reject = (result) =>
{
console.log(result)
}
// 3. 执行回调函数
func(resolve, reject)
}
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
// reject('error')
})
-
创建构造函数,并由 func接收传入的回调函数 -
设置回调函数的两个参数,resolve,reject函数 -
执行func回调函数
2.2 更改状态和设置原因
//1. 设置三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise
{
constructor(func)
{
//2. 设置result存储原因 /初始化状态
this.state = PENDING
this.result = undefined
const resolve = (result) =>
{
//3. 通过resolve/reject修改状态
//注意判断状态条件 使得状态不可逆
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
}
}
const reject = (result) =>
{
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
}
}
func(resolve, reject)
}
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
reject('error')
})
-
设置三种状态,pending,fufilled,rejected。 -
初始化实例状态为pending -
设置result变量存储resolve、reject传递过来的结果 -
执行resolve、reject函数时候,修改状态 -
注意状态的不可逆,状态只能由pending -> fulfilled或者pending -> rejected,不可以从fulfilled -> rejected,条件判断一下,即便resolve,reject都执行了,实例状态还是由第一个执行的函数改变
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
reject('error')
})
2.3 初步实现then方法
class MyPromise
{
constructor(func)
{
//...
}
// 1. 添加实例方法
then (onFulfilled, onRejected)
{
// 2. 参数判断
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
// 2.1 执行成功回调
// 2.2 执行失败回调
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else if (this.state === REJECTED) {
onRejected(this.result)
}
}
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
// reject('error')
})
p.then(res =>
{
console.log('p1:', res) // p1: success
})
-
添加实例then方法,接收两个参数,一个执行 成功的回调函数onFulfilled,一个执行失败的回调函数onRejected -
作为调用者,对then方法的参数可能设置有不同形式,可能是简单的变量或是正确的回调函数形式,因此在then方法内部需要对传递过来的参数进行一个判断 -
使用 typeof关键字对参数类型进行判断,对于onFulfilled和onRejected,如果是回调函数都不做处理-
onFulfilled如果不是回调函数,其内部转化为一个恒等函数((x) => x),只是简单的把值传递一下,也没有什么处理 -
mdn上这么说:如果onFulfilled不是一个函数,则内部会被替换为一个 恒等函数((x) => x),它只是简单地将兑现值向前传递。 -
onRejected如果不是回调函数,对其抛出其拒绝的原因 -
mdn上这么说:如果onRejected不是一个函数,则内部会被替换为一个 抛出器函数((x) => { throw x; }),它会抛出它收到的拒绝原因
-
-
关键在于状态判断:根据实例的状态来执行不同的回调函数-
FULFILLED状态:执行第一个回调函数onFulfilled回调,并把 原因(结果),传递给then方法的参数 -
REJECTED状态:执行第二个回调函数onRejected回调,并把 原因(结果),传递给then方法的参数
-
-
通过以上处理,then方法就可以正确调用,并可以根据不同状态执行不同的回调函数,来接收对应的结果
2.4 支持异步操作和多次调用
此时then方法为同步
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
})
p.then(res =>
{
console.log('p1:', res) //p1: success
})
-
根据目前我们实现的代码,通过调用resolve函数改变实例状态,通过then方法根据状态处理不同的结果,成功打印success -
Promise是支持异步操作resolve/reject函数,如果使用我们的代码直接设置异步操作,能否正确输出结果呢?
const p = new MyPromise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('success')
}, 2000);
})
p.then(res =>
{
console.log('p1:', res)
})
-
为什么无法正常输出呢?设置一个定时器异步操作resolve为什么就无法在then方法中正确处理结果呢?在控制台打印一下此时的实例状态
-
很明显,p的状态根据设置定时器,初始为pending,定时器结束后,执行resolve设置为fulfilled状态,那么想到此时的 then方法是同步的,它不会等到定时器结束才执行 -
一开始实例状态是pending,直接执行了then方法,但我们没有在then方法中对pending状态做出处理,也就没有正确执行回调,所以就什么都没有了 -
那么怎么正确处理呢? 先保存此时的回调和结果,等状态改变了再执行
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise
{
constructor(func)
{
//...
//保存成功的回调函数
this.onResolveCallbacks = []
//保存失败的回调函数
this.onRejectCallbacks = []
const resolve = (result) =>
{
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
//遍历执行保存的回调函数
this.onResolveCallbacks.forEach(fn => fn())
}
}
const reject = (result) =>
{
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
//遍历执行保存的回调函数
this.onRejectCallbacks.forEach(fn => fn())
}
}
func(resolve, reject)
}
then (onFulfilled, onRejected)
{
//...
if (this.state === FULFILLED) {
onFulfilled(this.result)
} else if (this.state === REJECTED) {
onRejected(this.result)
} else if (this.state === PENDING) {
//PENDING状态处理
this.onResolveCallbacks.push(() =>
{
onFulfilled(this.result)
})
this.onRejectCallbacks.push(() =>
{
onRejected(this.result)
})
}
}
}
const p = new MyPromise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('success')
}, 2000);
})
p.then(res =>
{
console.log('p1:', res) //p1: success
})
-
添加两个数组用于存储执行成功和失败的回调函数,在状态为pending的时候先添加到对应数组中 -
等待调用resolve/reject改变状态后,再依次从数组中取出对应成功or失败的回调函数并执行 -
对于多次调用 then方法,由于我们用数组存储回调函数,即可以多次调用then方法了(非链式调用)
const p = new MyPromise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('success')
}, 2000);
})
p.then(res =>
{
console.log('p1:', res)
})
p.then(res =>
{
console.log('p1:', res)
})
p.then(res =>
{
console.log('p1:', res)
})
-
这次就成功打印结果了
2.5 then的异步执行
在2.4中,我们实现了异步操作resolve/reject,能够成功处理对应结果,但当时的
then方法还是同步的
在Promise中
then方法是异步的
console.log(1);
const p1 = new Promise((resolve, reject) =>
{
resolve('success')
})
p1.then(res =>
{
console.log(res);
})
console.log(2);
执行结果为: 1 2 success
在MyPromise中
我们未对then方法的调用处理异步,此时还是同步的
console.log(1);
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
})
p.then(res =>
{
console.log('p1:', res)
})
console.log(2);
执行结果为:1 p1: success 2
使用settimeout处理异步
then (onFulfilled, onRejected)
{
//...
if (this.state === FULFILLED) {
setTimeout(() =>
{
onFulfilled(this.result)
}, 0);
} else if (this.state === REJECTED) {
setTimeout(() =>
{
onRejected(this.result)
}, 0);
} else if (this.state === PENDING) {
//执行回调
this.onResolveCallbacks.push(() =>
{
setTimeout(() =>
{
onFulfilled(this.result)
}, 0);
})
this.onRejectCallbacks.push(() =>
{
setTimeout(() =>
{
onRejected(this.result)
}, 0);
})
}
}
}
-
通过 setTimeout 方法将 then 方法的执行放到宏任务中执行,在同步任务执行完毕后,then 方法才会被调用
2.6 then的链式调用
对于then的链式调用即需要实现很多关键点
-
返回一个新的MyPromise实例可以继续调用then方法 -
获取实例的返回值并处理异常 -
对于不同返回值做出不同的处理 -
普通返回值 -
返回值是MyPromise实例 -
返回值是对应的实例(重复引用)即该抛出错误
-
-
对于三种状态统一进行处理
返回一个新实例
then (onFulfilled, onRejected)
{
//...
const p2 = new MyPromise((resolve, reject) =>
{
//...
})
return p2
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
})
p.then(res =>
{
console.log('p1:', res)
return 1
}).then(res =>
{
console.log('p2:', res)
})
-
可以链式调用then方法不会报错,但是第二个then方法明显无法第一个then方法的返回值,
说明未对第一个实例的返回值进行处理 -
在Promise中,第一个then方法的返回值会传递给第二个链式调用的then方法的第一个回调函数的参数,即第二个then应该打印p2: 1
获取实例的返回值并对异常做出处理
then (onFulfilled, onRejected)
{
//...
const p2 = new MyPromise((resolve, reject) =>
{
if (this.state === FULFILLED) {
setTimeout(() =>
{
try {
const res = onFulfilled(this.result)
//将实例返回值传递给下一个then方法的onFulfilled回调函数
resolve(res)
} catch (error) {
//处理异常 将错误信息传递给下一个then方法的onRjected回调函数
reject(error)
}
}, 0);
}
//...
return p2
}
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
})
p.then(res =>
{
console.log('p1:', res)
return 1
}).then(res =>
{
console.log('p2:', res)
})
-
获取onFulfilled回调函数的返回值,即是then方法中第一个函数的返回值 -
异常处理,在then方法中,可能会抛出各种异常错误,因此需要利用try catch对异常做出处理-
没有异常,就调用resolve函数,将返回值传递给下一个then方法的onFulfilled回调函数 -
有异常,就调用reject函数,将error捕获的错误传递给下一个then方法的onRejected回调函数
-
-
then方法中抛出错误
p.then(res =>
{
console.log('p1:', res)
throw 'xxxx'
return 1
}).then(res =>
{
console.log('p2:', res)
}, error =>
{
console.log('p2:', error)
})
当返回值为MyPromise实例
then (onFulfilled, onRejected)
{
//...
const p2 = new MyPromise((resolve, reject) =>
{
if (this.state === FULFILLED) {
setTimeout(() =>
{
try {
const res = onFulfilled(this.result)
//1. 处理返回MyPromise实例
if (res instanceof MyPromise) {
res.then(res => resolve(res), error => reject(error))
} else {
//将实例返回值传递给下一个then方法的onFulfilled回调函数
resolve(res)
}
} catch (error) {
//处理异常 将错误信息传递给下一个then方法的onRjected回调函数
reject(error)
}
}, 0);
//...
}
})
return p2
}
}
const p = new MyPromise((resolve, reject) =>
{
resolve('success')
})
p.then(res =>
{
console.log('p1:', res)
return new MyPromise((resolve, reject) =>
{
resolve(2)
// reject('error')
})
}).then(res =>
{
console.log('p2:', res) //2
}, error =>
{
console.log('p2:', error)
})
-
当第一个then方法返回值如果是MyPromise实例的时候 -
我们利用 instanceof关键判断,res获取的返回值是否是MyPromise实例,如果是该实例 -
通过then方法先获取对应的结果,再通过resolve/reject将结果传递给下一个then方法的回调函数
当返回值和接受返回值的实例一致(重复引用)
const p2 = new MyPromise((resolve, reject) =>
{
if (this.state === FULFILLED) {
setTimeout(() =>
{
try {
const res = onFulfilled(this.result)
// 处理重复引用
if (res === p2) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
}
//处理返回MyPromise实例
if (res instanceof MyPromise) {
res.then(res => resolve(res), error => reject(error))
} else {
//将实例返回值传递给下一个then方法的onFulfilled回调函数
resolve(res)
}
} catch (error) {
//处理异常 将错误信息传递给下一个then方法的onRjected回调函数
reject(error)
}
}, 0);
}
//...
})
return p2
const p2 = p.then(res =>
{
console.log('p1:', res)
return p2
})
p2.then(res =>
{
console.log('p2:', res)
}, error =>
{
console.log('p2:', error)
})
-
当p2 接收then方法的返回值时,返回值也是p2,在Promise中会直接抛出错误 -
因此我们也需要判断res 和 p2是否一致,如果一致就是重复引用,直接抛出错误,有catch捕获错误,并利用reject传递给下一个then方法中的onRejected回调函数中
抽离函数
-
我们不仅需要在实例状态为onFulfilled时候,对返回值进行处理,对于其余两个状态也是需要相同的处理,为了实现代码复用,将上述处理过程抽离成一个函数 -
在三种状态中依次调用该方法
//封装函数 处理then方法
function resolvePromise (p2, res, resolve, reject)
{
//1. 重复引用 直接抛出错误
if (res === p2) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
}
//2. 返回值是MyPromise实例 利用then获取对应的resolve/reject的值
if (res instanceof MyPromise) {
res.then(res => resolve(res), error => reject(error))
} else {
//3.处理普通返回值
resolve(res)
}
}
//then方法 两个参数 onFulfilled, onRejected
then (onFulfilled, onRejected)
{
//...
const p2 = new MyPromise((resolve, reject) =>
{
if (this.state === FULFILLED) {
setTimeout(() =>
{
try {
//...
resolvePromise(p2, res, resolve, reject)
}
}, 0);
} else if (this.state === REJECTED) {
setTimeout(() =>
{
try {
//...
resolvePromise(p2, res, resolve, reject)
}
}, 0);
} else if (this.state === PENDING) {
this.onResolveCallbacks.push(() =>
{
setTimeout(() =>
{
try {
//...
resolvePromise(p2, res, resolve, reject)
}
}, 0);
})
this.onRejectCallbacks.push(() =>
{
setTimeout(() =>
{
try {
//...
resolvePromise(p2, res, resolve, reject)
}
}, 0);
})
}
})
return p2
}
三:实现Promise实例方法
核心功能实现之后,开始去实现,Promise实例上的方法
3.1 catch方法
在之前的示例中有出现过catch方法的示例,也大体讲解了下catch方法与then方法之间的联系与关系
引用MDN中对catch方法的论述:
Promise实例的 「catch()」 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的Promise对象,这可以允许你 链式调用其他 promise 的方法此方法是 Promise.prototype.then(undefined, onRejected)的一种简写形式
总结一下:
catch方法等同于then(undefined, onRejected)
-
catch()方法用于捕获并处理 Promise 实例的拒绝状态。当 Promise 实例被拒绝时,catch()方法会被调用,并传递拒绝的原因作为参数。 -
返回一个 Promise实例
实现
参数(onRejected):一个在此 Promise 对象被拒绝时异步执行的函数。它的返回值将成为
catch()返回的 Promise 对象的兑现值返回值:返回一个新Promise,新Promise在返回时总处于待定状态
catch (onRejected)
{
return this.then(undefined, onRejected)
}
const p = new MyPromise((resolve, reject) =>
{
reject('error')
})
p.then(res =>
{
console.log(res)
}).catch(error =>
{
console.log('p:', error); //p: error
})
3.2 finally方法
Promise实例的 「finally()」 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的Promise对象,这可以允许你链式调用其他 promise 方法。如果你想在 promise 敲定时进行一些处理或者清理,无论其结果如何,那么
finally()方法会很有用。
总结一下:
-
无论该Promise实例最终是被成功兑现(resolve),还是被拒绝(reject),finally方法都会被调用 -
返回一个Promise实例
实现
参数(onFinally):回调函数
返回值:返回一个Promise实例
//finally方法
finally (onFinally)
{
return this.then(onFinally, onFinally)
}
const p = new MyPromise((resolve, reject) =>
{
// reject('error')
resolve('success')
})
p.then(res =>
{
console.log(res)
}).catch(error =>
{
console.log('p:', error);
}).finally(() =>
{
console.log(1); // success 1
})
-
无论是成功的兑现resolve('success'),还是拒绝reject('error'),最终都会调用finally函数并执行其中的回调函数
四:实现Promise静态方法
Promise还提供了一些静态方法,是在构造函数上调用的,而不是在实例上调用的
4.1 resolve方法
「
Promise.resolve()」 静态方法将给定的值转换为一个Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve()将调用其then()方法及其两个回调函数
Promise中的resolve方法
const p = Promise.resolve(1)
console.log(p);
p1 = Promise.resolve(p)
console.log(p1);
-
Promise.resolve,将给定的1转化为了一个Promise对象 -
如果再把一个Promise对象传递给Promise.resolve中,它不会继续封装该Promise,而是将这个Promise对象返回
思路
-
当接收的值是一个Promise对象时候,直接返回该对象 -
否则,返回一个新Promise对象,并调用resolve,兑现对应的值
实现
参数(value):resolve方法接收的值,可能为MyPromise
返回值:MyPromise
static resolve (value)
{
//是一个MyPromise直接返回
if (value instanceof MyPromise) {
return value
}
//其余转化为MyPromise并返回
return new MyPromise(resolve =>
{
resolve(value)
})
}
-
利用instanceof关键字判断value是否为一个MyPromises实例,如果是,直接返回该MyPromise -
返回一个Promise实例,并调用resolve兑现该值, 即该实例状态直接被转为fulfilled
const p = MyPromise.resolve(1)
console.log(p);
const p1 = MyPromise.resolve(p)
console.log(p1);
4.2 reject方法
「
Promise.reject()」 静态方法返回一个已拒绝(rejected)的Promise对象,拒绝原因为给定的参数。
这个说的就很清楚
-
返回一个Promise对象 -
调用reject更改状态为rejected拒绝,并把原因传递过去
实现
参数(value):该
Promise对象被拒绝的原因。返回值:Promise对象
static reject (value)
{
//直接返回一个Promise
return new MyPromise((undefined, reject) =>
{
reject(value)
})
}
const p = MyPromise.reject('error')
p.catch(error =>
{
console.log(error);
})
4.3 race方法
「
Promise.race()」 静态方法接受一个 promise 可迭代对象作为输入,并返回一个Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
总结一下
-
接收一个promise可迭代对象,例如为数组,元素都为promise -
数组中的所有promise实例进行竞争,谁敲定的最快(状态改变的最快),就返回谁的Promise实例
在promise中
const p1 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('p1')
}, 1000);
})
const p2 = 'p2'
const p3 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
reject('p3')
}, 3000);
})
const p = Promise.race([p1, p2, p3])
p.then(res =>
{
console.log(res);
}).catch(error =>
{
console.log(error);
})
-
设定两个Promise实例和一个基本类型值,p1实例设置定时器1秒后兑现结果,p2为基本类型值'p2',p3设置一个定时器3秒后拒绝 -
调用Promise.race()方法,并把p1,p2,p3作为数组传递race方法 -
race方法内部,会先将不是promise对象转化为promise对象,然后所有Promise实例进行竞争,显然没有设置定时器的p2实例最快,返回p2实例 -
最终通过then方法兑现对应的结果
思路
-
接收一个数组,元素都为Promise实例,那么如果接受的不是数组呢?抛出错误 -
返回值为一个Promise实例 -
遍历数组,把不是Promise实例的转化为Promise实例 -
等待第一个Promise的敲定(状态改变)
实现
参数(iterable):可迭代对象,例如数组
返回值:Promise
static race (promises)
{
//返回一个promsie
return new MyPromise((resolve, reject) =>
{
//如果参数不是一个数组直接抛出错误
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
//遍历数组等待第一个敲定
promises.forEach(p =>
{
//由于p不一定是一个promise 先转化为promise
MyPromise.resolve(p).then(res =>
{
resolve(res)
}, error =>
{
reject(error)
})
})
})
}
-
首先返回一个MyPromise实例,在其中写对应逻辑 -
利用Array.isArray方法判断是否为一个数组,不是数组抛出错误 -
遍历数组,通过resolve方法将不是Promise实例转化为Promise实例 -
调用then方法,接收Promise实例敲定后传递的结果或拒绝的原因 -
谁最先敲定(改变状态),谁直接调用resolve/reject把结果或原因返回
4.4 all方法
「
Promise.all()」 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
总结一下
-
跟race方法接收参数形式一样,都是一个数组,元素为Promise实例 -
返回值为一个Promise实例 -
等所有的Promise实例全部都被兑现了,并将兑现的值按照Promise传入数组的位置,依次存放到一个数组中 -
最后将返回值的Promise实例兑现,并把这个结果数组传递 -
只要其中有Promise拒绝了,那么返回的Promise就被拒绝,并把原因传递
在Promise中
const p1 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('p1')
}, 1000);
})
const p2 = 'p2'
const p3 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('p3')
}, 3000);
})
const p = Promise.all([p1, p2, p3])
p.then(res =>
{
console.log(res);
}).catch(error =>
{
console.log(error);
})
-
当数组中的所有Promise实例全部被兑现,兑现的值也会按照对应位置存放在一个数组中 -
等所有Promise实例都被兑现了,最终兑现返回的promise实例,并把这个结果数组传递
const p1 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('p1')
}, 1000);
})
const p2 = 'p2'
const p3 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
reject('p3')
}, 3000);
})
const p = Promise.all([p1, p2, p3])
p.then(res =>
{
console.log(res);
}).catch(error =>
{
console.log(error);
})
-
但如果只要有一个promise实例被拒绝了,返回的promise直接拒绝
思路
-
判断参数,是否是一个数组,不是数组就抛出错误 -
返回一个Promise实例 -
如果是空数组,直接兑现 -
遍历数组,把数组中不是Promise实例的转化为Promise实例 -
维护一个数组和一个计数器 -
遍历数组,等待所有Primose兑现 -
通过索引记录兑现值在数组中的位置,保证跟参数中顺序一致 -
利用计数器计数,等待所有promise实例兑现
-
实现
static all (promises)
{
//返回一个promise
return new MyPromise((resolve, reject) =>
{
//不是数组抛出错误
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
//空数组直接兑现
promises.length === 0 && resolve(promises)
const result = []
let count = 0
//foreach取出全部的promise
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
//记录结果
result[index] = res
//判断全部兑现 传递result
count++
count === promises.length && resolve(result)
}, error =>
{
//处理拒绝
reject(error)
})
})
})
}
-
还是利用Array.isArray方法判断参数是否为一个数组 -
判断参数数组是否为空数组,空数组直接兑现,并把空数组传递 -
遍历数组中,利用resolve方法把不是promise实例的元素转化为priomise实例 -
通过result[index]索引记录对应的promise实例的兑现值 -
count计数器等所有的promise实例兑现,最后传递result数组
4.5 allSettled方法
「
Promise.allSettled()」 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组
总结一下
-
接受参数和返回值与all方法类似 -
等全部promise实例敲定时(all方法是兑现时),也就意味着allSettled方法允许出现被拒绝时的状态,不会像all方法一样被拒绝时,返回值也直接被拒绝
在promise中
const p1 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
resolve('p1')
}, 1000);
})
const p2 = 'p2'
const p3 = new Promise((resolve, reject) =>
{
setTimeout(() =>
{
reject('p3')
}, 3000);
})
const p = Promise.allSettled([p1, p2, p3])
p.then(res =>
{
console.log(res);
}).catch(error =>
{
console.log(error);
})
-
与all方法不同的是,当数组中的promise实例有被拒绝的状态的时候,返回的promise也不会直接被拒绝,而是会把这个状态记录,原因传递,其余promise继续兑现 -
同时,与all方法不同的是,最终传递的结果数组中是以对象形式 -
status为fulfilled状态时候,另个属性就是value,兑现值 -
statue为rejected状态时候,另个属性就是reason,拒绝原因
-
-
但大体内层逻辑与all方法大体相同
实现
//settled方法
static allSettled (promises)
{
//返回promise
return new MyPromise((resolve, reject) =>
{
//判断数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 为空
promises.length === 0 && resolve(promises)
const result = []
let count = 0
//遍历数组
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
//记录
result[index] = { status: FULFILLED, value: res }
count++
count === promises.length && resolve(result)
}, error =>
{
result[index] = { status: REJECTED, reason: error }
count++
count === promises.length && resolve(result)
})
})
})
}
-
与all方法不同的是 -
在兑现或拒绝每个promise实例的时候,对于索引记录下是以对象形式存入数组中 -
无论兑现还是拒绝,利用计数器等到全部promise敲定之后再将返回值敲定,并把结果数组传递
4.6 any方法
「
Promise.any()」 静态方法将一个 Promise 可迭代对象作为输入,并返回一个Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的AggregateError拒绝。
总结一下
-
接收参数和返回值与all方法类似 -
但有种像all方法反过来的感觉 -
只要有一个promise兑现,返回值promise立即兑现 -
all方法是只要有一个promise被拒绝,返回值promise直接被拒绝
-
-
只有当全部promise实例都被拒绝时候,返回值promise才会被拒绝,并传递一个拒绝原因的数组
实现
static any (promises)
{
// 1. 返回Promise,数组判断
return new MyPromise((resolve, reject) =>
{
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 2. 空数组直接拒绝
promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))
// 3. 等待结果
const errors = []
let count = 0
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
// 3.1 第一个兑现
resolve(res)
}, err =>
{
// 3.2 全部拒绝
errors[index] = err
count++
count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'))
})
})
})
}
}
-
逻辑上是all方法的反过来
五:完整代码
function resolvePromise (p2, res, resolve, reject)
{
//1. 重复引用 直接抛出错误
if (res === p2) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
}
//2. 返回值是MyPromise实例 利用then获取对应的resolve/reject的值
if (res instanceof MyPromise) {
res.then(res => resolve(res), error => reject(error))
} else {
//3.处理普通返回值
resolve(res)
}
}
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise
{
constructor(func)
{
//初始状态pending
this.state = PENDING
//初始原因undefined
this.result = undefined
//保存成功的回调函数
this.onResolveCallbacks = []
//保存失败的回调函数
this.onRejectCallbacks = []
//更改状态
const resolve = (result) =>
{
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
//遍历执行保存的回调函数
this.onResolveCallbacks.forEach(fn => fn())
}
};
const reject = (result) =>
{
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
//遍历执行保存的回调函数
this.onRejectCallbacks.forEach(fn => fn())
}
};
//捕获错误
try {
//执行函数
func(resolve, reject)
} catch (error) {
reject(error)
}
}
//then方法 两个参数 onFulfilled, onRejected
then (onFulfilled, onRejected)
{
//参数判断
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
//链式调用 返回新的promise实例
const p2 = new MyPromise((resolve, reject) =>
{
//根据状态执行成功失败的回调
if (this.state === FULFILLED) {
//并把失败的原因传递过去
setTimeout(() =>
{
//处理异常 调用reject并把异常传过去
try {
//res为p1的返回值 判断是否为promise
const res = onFulfilled(this.result)
resolvePromise(p2, res, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
} else if (this.state === REJECTED) {
setTimeout(() =>
{
//使用try catch捕获错误
try {
//获取p1返回值
const res = onRejected(this.result)
resolvePromise(p2, res, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
} else if (this.state === PENDING) {
//保存成功的回调
this.onResolveCallbacks.push(() =>
{
setTimeout(() =>
{
//处理异常
try {
//获取返回值
const res = onFulfilled(this.result)
resolvePromise(p2, res, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
//保存失败的回调
this.onRejectCallbacks.push(() =>
{
setTimeout(() =>
{
//处理异常
try {
//获取返回值
const res = onRejected(this.result)
resolvePromise(p2, res, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
}
})
//返回新的promise实例实现链式调用
return p2
}
//catch方法
catch (onRejected)
{
//调用then中的onRejected回调
return this.then(undefined, onRejected)
}
//finally方法
finally (onFinally)
{
return this.then(onFinally, onFinally)
}
//resolve方法
static resolve (value)
{
//是一个MyPromise直接返回
if (value instanceof MyPromise) {
return value
}
//其余转化为MyPromise并返回
return new MyPromise(resolve =>
{
resolve(value)
})
}
//reject方法
static reject (value)
{
//直接返回一个Promise
return new MyPromise((undefined, reject) =>
{
reject(value)
})
}
//race方法
static race (promises)
{
//返回一个promsie
return new MyPromise((resolve, reject) =>
{
//如果参数不是一个数组直接抛出错误
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
//遍历数组等待第一个敲定
promises.forEach(p =>
{
//由于p不一定是一个promise 先转化为promise
MyPromise.resolve(p).then(res =>
{
resolve(res)
}, error =>
{
reject(error)
})
})
})
}
//all方法
static all (promises)
{
//返回一个promise
return new MyPromise((resolve, reject) =>
{
//不是数组抛出错误
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
//空数组直接兑现
promises.length === 0 && resolve(promises)
const result = []
let count = 0
//foreach取出全部的promise
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
//记录结果
result[index] = res
//判断全部兑现 传递result
count++
count === promises.length && resolve(result)
}, error =>
{
//处理拒绝
reject(error)
})
})
})
}
//settled方法
static allSettled (promises)
{
//返回promise
return new MyPromise((resolve, reject) =>
{
//判断数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 为空
promises.length === 0 && resolve(promises)
const result = []
let count = 0
//遍历数组
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
//记录
result[index] = { status: FULFILLED, value: res }
count++
count === promises.length && resolve(result)
}, error =>
{
result[index] = { status: REJECTED, reason: error }
count++
count === promises.length && resolve(result)
})
})
})
}
//any方法
static any (promises)
{
// 1. 返回Promise,数组判断
return new MyPromise((resolve, reject) =>
{
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 2. 空数组直接拒绝
promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))
// 3. 等待结果
const errors = []
let count = 0
promises.forEach((p, index) =>
{
MyPromise.resolve(p).then(res =>
{
// 3.1 第一个兑现
resolve(res)
}, err =>
{
// 3.2 全部拒绝
errors[index] = err
count++
count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'))
})
})
})
}
}
六:总结
写完身心俱疲,但又有种知识充斥在脑海中的感觉,以前也只是了解会用promise的几种方法,也不了解其中方法的逻辑,
只有对其实现一下,才会对其更加的掌握,现在对promise有了更深的了解了,希望在以后使用promise的时候,调用其中的方法时候,能够想到其中的逻辑是怎么实现的,这样能更放心的去使用
参考文章:
-
MDN官方Promise
本文由 mdnice 多平台发布
相关文章:
实现Promise所有核心功能和方法
一直以来对Promise只是会用简单的方法,例如then,catch等,对于其余各种方法也只是简单了解,这次想要通过实现Promise来加深对Promise的使用 话不多说,直接开始,简单粗暴一步步来 一:了解Promise …...
学习总结1
Vue的学习 Vue是一套用于构建用户界面的渐进式JavaScript框架; Vue中关键的几个概念:组件化,MVVM,响应式,和生命周期。 1. 组件化: 在Vue框架中,允许你将界面拆分为小的,独立的可…...
使用 Apache Camel 和 Quarkus 的微服务(二)
【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 在本系列的第一部分,我们看到了一个简化版的基于微服务的转账应用程序,该应用程序使用Apache Camel和AWS SDK(软件开发套件…...
pid-limit参数实验
fork炸弹命令 :(){ :|:& };: 可以看到,如果docker没有限制,会遭到fork炸弹恶意 参考 https://www.cyberciti.biz/faq/understanding-bash-fork-bomb/...
jvm--执行引擎
文章目录 1. 执行引擎的工作流程2. 解释器、JIT及时编译器3. 热点代码及探测技术4. HotSpotVM 中 JIT 分类 执行引擎属于 JVM 的下层,里面包括解释器、及时编译器、垃圾回收器 JVM 的主要任务是负责 装载字节码到其内部,但字节码并不能够直接运行在操作…...
day13|二叉树理论
一、二叉树的定义 //定义一个二叉树:使用链式存储 public class TreeNode {int val; // 节点的值TreeNode left; // 左子节点TreeNode right; // 右子节点public TreeNode() {}// 构造函数,初始化节点值public TreeNode(int val){this.valval;}// 构造函…...
php+html+js+ajax实现文件上传
phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…...
日期时间参数,格式配置(SpringBoot)
介绍 在SpringBoot项目中,接口中的日期和时间类型的参数,配置格式。 日期格式 接口中常用的日期时间格式有两种: 字符串(比如:yyyy-MM-dd HH:mm:ss)时间戳(比如:1696839876955&a…...
JAVA 泛型的定义以及使用
泛型类 /*** <T> 为该类定义泛型,可以是一个或多个<T,...>* 定义的泛型可以在类中作为:* 类变量类型: T data* 类方法的入参以及返回类型 public void setData(T data),public T getData();次数以set&a…...
Day-08 基于 Docker安装 Nginx 镜像-负载均衡
1、反向代理后,自然而然就引出了负载均衡,下面简单实现负载均衡的效果; 2、实现该效果需要再添加一个 Nginx ,所以要增加一个文件夹。 /home|---mutou|----nginx|----conf.d|----html|----conf.d2|----html3 1.创建 html3 文件夹, 新建 index…...
3、在 CentOS 8 系统上安装 PostgreSQL 15.4
PostgreSQL,作为一款备受欢迎的开源关系数据库管理系统(RDBMS),已经存在了三十多年的历史。它提供了SQL语言支持,用于管理数据库和执行CRUD操作(创建、读取、更新、删除)。 由于其卓越的健壮性…...
sap 一次性供应商 供应商账户组 临时供应商 <转载>
原文链接:https://blog.csdn.net/xianshengsun/article/details/132620593 sap中有一次性供应商这个名词,一次性供应商和非一次性供应商又有什么区别呢? 有如何区分一次性供应商和非一次性供应商呢? 1 区分一次性供应商和非一次性…...
总结html5中常见的选择器
HTML5并没有引入新的选择器类型,它仍然使用CSS选择器来选择和操作HTML元素。HTML5中仍然可以使用CSS2和CSS3中定义的各种选择器。以下是HTML5中常见的选择器类型: 1. 元素选择器(Element Selector):使用元素名称作为选…...
Java基础面试-JDK JRE JVM
详细解释 JDK(Java Devalpment Kit)java 开发工具 JDK是Java开发工具包,它是Java开发者用于编写、编译、调试和运行Java程序的核心组件。JDK包含了Java编程语言的开发工具和工具集,以及Java标准库和其他一些必要的文件。JDK中的…...
OpenCV实现图像傅里叶变换
傅里叶变换 dftcv.dft(img_float32,flagscv.DFT_COMPLEX_OUTPUT): flags:标志位,指定变换类型,cv.DFT_COMPLEX_OUTPUT会返回复数结果。 傅立叶变换,将输入的图像从空间域转换到频率域。 返回结果: 此函数返回一个复杂数值数组,…...
快手新版本sig3参数算法还原
Frida Native层主动调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81…...
Linux 安全 - LSM机制
文章目录 前言一、LSM起源二、LSM简介2.1 MAC2.2 LSM特征 三、Major and Minor LSMs3.1 Major LSMs3.2 Minor LSMs3.3 BPF LSM 四、LSM 框架五、LSM Capabilities Module六、LSM hooks 说明参考资料 前言 在这两篇文章中介绍了 Linux 安全机制 Credentials : Linu…...
uni-app:实现简易自定义下拉列表
效果 代码 <template><view><view class"dropdown-trigger" tap"showDropdown">{{ selectedItem }}</view><view class"dropdown-list" v-if"showList"><view class"dropdown-item" v-f…...
排序算法——直接插入排序
一、介绍 插入排序就是将前两个元素排好,再将第三个元素通过与前边的元素比较后插入适当的位置,再将第四个元素插入,不断重复插入与前边元素比较的操作,直到将元素都排列好。 演示如下: 视频演示:…...
手动抄表和自动抄表优缺点对比
随着科技的发展,自动抄表技术已经越来越成熟,被广泛应用于各个领域。然而,手动抄表在一些特定场景下仍然具有一定的优势。本文将从手动抄表和自动抄表的优缺点入手,对比分析它们的应用场景和使用价值。 1.成本低:手动抄…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...
VSCode 使用CMake 构建 Qt 5 窗口程序
首先,目录结构如下图: 运行效果: cmake -B build cmake --build build 运行: windeployqt.exe F:\testQt5\build\Debug\app.exe main.cpp #include "mainwindow.h"#include <QAppli...
SQLSERVER-DB操作记录
在SQL Server中,将查询结果放入一张新表可以通过几种方法实现。 方法1:使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构(包括列名和数据类型)将与查询结果匹配。 SELECT * INTO 新…...
2025年全国I卷数学压轴题解答
第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos x − cos ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min t max x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...
Spring Boot 中实现 HTTPS 加密通信及常见问题排查指南
Spring Boot 中实现 HTTPS 加密通信及常见问题排查指南 在金融行业安全审计中,未启用HTTPS的Web应用被列为高危漏洞。通过正确配置HTTPS,可将中间人攻击风险降低98%——本文将全面解析Spring Boot中HTTPS的实现方案与实战避坑指南。 一、HTTPS 核心原理与…...
JavaScript性能优化实战大纲
性能优化的核心目标 降低页面加载时间,减少内存占用,提高代码执行效率,确保流畅的用户体验。 代码层面的优化 减少全局变量使用,避免内存泄漏 // 不好的实践 var globalVar I am global;// 好的实践 (function() {var localV…...
matlab实现DBR激光器计算
DBR激光器计算程序。非常值得参考的程序。DBR激光器程序 DBR计算/1.txt , 2056 DBR计算/4.asv , 22 DBR计算/4.txt , 32 DBR计算/GetDeviceEfficiency.asv , 2012 DBR计算/GetDeviceEfficiency.m , 2014 DBR计算/GetOneLayerArray.asv , 837 DBR计算/GetOneLayerArray.m , 836…...
