Promise静态方法

本文最后更新于:8 个月前

Promise的几种静态方法介绍、适用场景、手写实现

Promise静态方法

Promise静态方法定义在构造函数里
image-20230209001400764

一、resolve()

1.1.介绍

Promise.resolve()可以把任何值都转换为一个Promise,它只对第一个参数起作用

1
2
3
Promise.resolve();			//Promise {<fulfilled>: undefined}
Promise.resolve(3); //Promise {<fulfilled>: 3}
Promise.resolve(4,5,6); //Promise {<fulfilled>: 4}

如果传入的参数是一个Promise,那么它相当于空包装(没有变化),因此resolve是一种幂等方法

1
2
3
let p = Promise.resolve(7);
p === Promise.resolve(p) //true
p === Promise.resolve(Promise.resolve(p)) //true

1.2.模拟实现

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
function myPromise(executor){
var self = this
self.status = 'pending'
self.value = undefined
self.reason = undefined
self.resolvedCallbacks = []
self.rejectedCallbacks = []

function resolve(value){
// 判断传入的元素是否为Promise实例,如果是,则状态改变必须等到前一个状态改变后再进行改变
if(value instanceof myPromise){
return value.then(resolve, reject)
}

// 用setTimeout保证代码的执行顺序为本轮事件的末尾(不是完全严谨,因为setTimeout是宏任务,而resolve是微任务)
setTimeout(()=>{
// 只有状态为pending时,才能转变
if(self.status === 'pending'){
self.status = 'fulfilled'
self.value = value

// 执行onResolved回调函数
self.resolvedCallbacks.forEach(cb=>{cb(self.value)})
}
}, 0)
}
...
}



二、reject()

1.介绍

Promise.reject()会实例化一个拒绝的Promise并抛出一个异步错误(这个错误只能通过reject处理程序捕获),reject()的第一个参数就是拒绝的reason,它会传给后续的处理程序。

1
2
let p = Promise.reject(3);		//报出一个 Uncaught(in promise)
p // Promise {<rejected>: 3}

reject不是幂等的,传给它一个Promise对象,会被当成它返回的reason

1
2
let p1 = Promise.reject(new Promise(()=>{}));		//Uncaught (in promise)
p1 // Promise {<rejected>: Promise}

在Promise的执行函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。Promise可以使用任何理由拒绝,但是最好使用统一的Error对象,因为可以捕获错误对象中的栈追踪信息,便于调试。


2.模拟实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function myPromise(executor){
var self = this
self.status = 'pending'
self.value = undefined
self.reason = undefined
self.resolvedCallbacks = []
self.rejectedCallbacks = []

function reject(reason){
setTimeout(()=>{
if(this.status === 'pending'){
self.status = 'rejected'
self.reason = reason
self.rejectedCallbacks.forEach((cb)=>{cb(self.reason)})
}
},0)
}
...
}



三、all()

1.介绍

Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决

参数:接受一组Promise实例,如果数组元素不是Promise实例,会自动调用Promise.resolve()将其转为Promise实例后再处理

返回值:一个新的Promise实例,PromiseResult是一个数组,包含了参数中所有Promise实例的result,顺序与参数中Promise实例对应

1
2
3
4
5
var all = Promise.all([
Promise.resolve('first'),
Promise.resolve('second')
])
all //Promise {<fulfilled>: ["first","second"]}

状态覆盖优先级:rejected > pending > resolved

如果参数中所有Promise实例都resolve了,返回值Promise的状态为resolved,PromiseResult为所有Promise的result按序组成的数组

1
2
3
4
5
var all = Promise.all([
Promise.resolve('first'),
Promise.resolve('second')
])
all //Promise {<fulfilled>: ["first","second"]}

如果参数中有rejected的Promise实例,第一个reject的Promise实例的reason作为返回值Promise实例的result(后面reject的reason不记录,但是它们的reject操作一样正常进行)

1
2
3
4
5
6
7
var all = Promise.all([
Promise.resolve('first'),
Promise.reject('second err'),
Promise.resolve('third'),
Promise.reject('forth err')
])
all //Promise {<rejected>: 'second err'}
1
2
3
4
5
var all = Promise.all([
Promise.resolve('first'),
new Promise(()=>'init')
])
all // Promise {<pending>}

2.模拟实现

all()实现要点:使用闭包保存参数中所有Promise实例的落定结果。如果全部都是resolved状态,将所有结果保存下来后进行resolve()返回;如果出现rejected状态,直接调用(不等结束)reject()返回。

  • 疑问:如果保证res闭包数组的结果顺序与参数Promise实例数组的顺序一致?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function myPromise(executor){
...

function all(iterator){
if(!Array.isArray(iterator)) return
let count = 0
let res = []
return new myPromise((resolve, reject)=>{
for(let item of iterator){
myPromise.resolve(item).then(data => {
res[count++] = {status:'fulfilled', value:data}
if(count === iterator.length) resolve(res)
}).catch(e=>{
reject({status:'rejected', reason:e})
})
}
})
}
}
// 这里有一个疑问:res保存的结果顺序可能与数组中的Promise顺序不是一一对应

3.使用场景

通过all()实现多个异步任务并行执行(eg:并发请求)

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
function async_1(){
return new Promise((resolve, reject)=>{
// 这里用setTimeout模拟异步任务,例如:异步http请求等等
setTimeout(()=>{
let data = 'data1'
resolve(data)
}, 0)
})
}

function async_2(){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
let data = 'data2'
resolve(data)
}, 100)
})
}

let async_works = [async_1(), async_2()] // 让异步任务先执行起来

// (所有Promise实例都resolved后)然后在Promise.all中,通过then()设置的回调函数获得异步任务的返回结果
Promise.all(async_works)
.then((result)=>{
console.log(result[0])
console.log(result[1])
})
.catch(err=>{
console.error(err)
})



四、allSettled()

1.介绍

它和Promise.all的区别就是:Promise.all 将在 Promises 数组中的其中一个 Promises 失败后立即失败。而Promise.allSettled 将永远不会失败,一旦数组中的所有 Promises 被完成或失败,它就会完成。

参数返回值与Promise.all一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
var all = Promise.allSettled([
Promise.resolve('first'),
Promise.reject('second err'),
Promise.resolve('third'),
Promise.reject('forth err')
])
all //Promise {<fulfilled>: Array(4)}

// 其中,Array(4)的组成如下:
{status: 'fulfilled', value: 'first'}
{status: 'rejected', reason: 'second err'}
{status: 'fulfilled', value: 'third'}
{status: 'rejected', reason: 'forth err'}

2.模拟实现

allSettled()实现要点:使用闭包保存参数数组中所有Promise的结果,不管是resolve还是reject,都保存下来,直到数组中全部元素都落定之后,调用新Promise实例的resolve()方法将所有结果返回

疑问:如果保证res数组保存的结果顺序,与参数数组中Promise实例的顺序一致?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function myPromise(executor){
...

function allSettled(promise_list){
if(!Array.isArray(iterator)) return
let count = 0
let res = []
return new Promise((resolve, reject)=>{
for(let promise of promise_list){
myPromise.resolve(promise) // 调用resolve,确保所有元素都是Promise实例
.then(data => res[count++] = {status:'fulfilled', value: data} )
.catch(err => res[count++]= {status:'rejected', reason: err})
.finally(()=>{
if(count === promise_list.length) resolve(res)
})
}
})
}
}



五、race()

1.介绍

返回一组集合中最先落定的Promise。不区分resolved还是rejected的Promise实例,只要是第一个“落定”(转换状态)了,Promise.race就包装它并返回。

参数:接受一组Promise实例,如果不是 Promise 实例,就会调用Promise.resolve()将参数转为Promise之后再处理

返回值:一个新的Promise实例,返回值Promise实例的result是参数中第一个resolve的Promise实例的result

1
2
3
4
5
6
7
var race = Promise.race([
Promise.reject(1),
Promise.resolve(2),
Promise.reject(3),
Promise.resolve(4)
])
race //Promise {<rejected>: 1}

2.模拟实现

race()的实现要点:只要参数数组中有一个Promise实例落定了,race()返回的新Promise实例就落定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function myPromise(executor){
...

function race(promise_list){
if(!Array.isArray(promise_list)) return
return new Promise((resolve, reject)=>{
promise_list.forEach((promise)=>{
Promise.resolve(promise)
.then(res => resolve(res))
.catch(err => reject(err))
})
})
}
}

3.使用场景

通过race()设置图片请求超时

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
// 请求图片资源,一旦加载完成,Promise的状态就发生改变
function fetchImg(url){
return new Promise((resolve,reject)=>{
var img = new Image()
img.onload = resolve
img.onerror = reject
img.src = url
})
}

// 延时函数,用于给请求计时
function timeout(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('请求超时')
}, 5000)
})
}

// fetchImg 和 timeout 竞争,如果图片超过5秒未加载,将返回一个rejected实例
Promise.race([fetchImg(), timeout()])
.then(result=>{
console.log(result)
})
.catch(err=>{
console.error(err)
})



六、any()

1.介绍

ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。


2.使用场景

1
2
3
4
5
6
7
8
9
Promise.any([
fetch(url_1).then(res=>res)
fetch(url_2).then(res=>res)
fetch(url_3).then(res=>res)
]).then((first)=>{
print(first)
}).catch(err=>{
console.error(err)
})

如上例,发送了3个不同url的请求,但实际上请求的是同一个数据。只要其中一个请求成功,any()返回的实例就会变成fulfilled。可以用来在不确定url是否可用的情况下,通过发送多个请求来增加请求成功的概率。


Promise静态方法
http://timegogo.top/2023/05/13/JavaScript/JavaScript异步(二)Promise静态方法/
作者
丘智聪
发布于
2023年5月13日
更新于
2023年7月16日
许可协议