Promise方法实现
狐七 3/13/2022 programming
# 手写一个Promise
查看简单答案
class Promise {
// 状态声明
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
// 执行器
constructor(executor) {
// 默认状态
this.status = PENDING
// 成功失败的值
this.value = undefined
this.reason = undefined
// 存放成功的回调
this.onResolveCallbacks = []
// 存放失败的回调
this.onRejectCallbakcs = []
const resolve = value => {
// 状态流转
if (this.status === PENDING) {
this.value = value
// 依次调用对应函数的执行
(this.onResolveCallbacks || []).forEach(fn => fn())
}
}
const reject = reason => {
// 状态流转
if (this.status === PENDING) {
this.reason = reason
// 依次调用对应函数的执行
(this.onRejectCallbacks || []).forEach(fn => fn())
}
}
try{
// 处理用户数据
executor(reslove, reject)
}catch(err) {
reject(err)
}
}
// then的两个回调
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
// PENDING状态将成功或者失败的状态分别存入数组中
if (this.status === PENDING) {
this.onResolveCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
}).then((value, err) => {
console.log(value)
})
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
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
查看完整答案
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
try {
exector(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if(this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while(this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if(this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while(this.failCallback.length) this.failCallback.shift()()
}
then (successCallback = value => value, failCallback = reason => {throw reason}) {
let promise2 = new Promise((resolve, reject) => {
if(this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
});
return promise2
}
finally (callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}
catch (failCallback) {
return this.then(undefined, failCallback)
}
static all (array) {
let result = []
let index = 0
return new Promise((resolve, reject) => {
let addData = (key, value) => {
result[key] = value
index ++
if(index === array.length) {
resolve(result)
}
}
for (let i = 0; i < array.lengt; i++) {
let current = array[i]
if (current instanceof MyPromise) {
current.then(value => addData(i, value), reason => reject(reason))
} else {
addData(i, array[i])
}
}
})
}
static resolve (value) {
if(value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if(x instanceof MyPromise) {
x.then(resolve, reject)
} else{
resolve(x)
}
}
module.exports = MyPromise
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 其他原生方法实现
# Promise.prototype.catch
Promise.prototype.catch = function (catchFuc) {
return this.then(null, catchFuc)
}
1
2
3
2
3
# Promise.prototype.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
)
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# Promise.resolve
Promise.resolve = function (value) {
return new Promise(( resolve, reject) => {
resolve(value)
})
}
1
2
3
4
5
2
3
4
5
# Promise.reject
Promise.reject = function (value) {
return new Promise((resolve, reject) => {
return reject(value)
})
}
1
2
3
4
5
2
3
4
5
# Promise.all
Promise.all = function(promiseArray) {
if(!Array.isArray(promiseArray)) {
throw new TypeError('...')
}
return new Promise((resolve, reject) => {
let resultArray = []
const length = promiseArray.length
let count = 0
for (let i = 0; i < length; i++) {
promiseArray[i].then(data => {
resultArray[i] = data
count++
if (count === length) {
resolve(resultArray)
}
})
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Promise.race
// 使用for循环
Promise.race = function (promiseArray) {
if(!Array.isArray(promiseArray)) {
throw new TypeError('...')
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for(let i = 0; i < length; i++) {
// 第一个返回直接resolve
promiseArray[i].then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
}
// 使用forEach
Promise.race = promises => new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject)
})
})
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Promise.any
Promise.any = function(arr) {
return new Promise((resolve, reject) => {
const len = arr.length
let count = 0
let result = []
for(let i = 0; i < len; i++) {
arr[i].then(data => {
resolve(data)
return;
}).catch(error => {
result[i] = error
count++
if (count === len) reject(result)
})
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Promise.allSettled
题目
const list = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(3),
Promise.reject(4)
]
Promise.allSettled(list).then(data => { console.log('result:', data) })
结果返回:
[
{ status: true, data: 1 },
{ status: true, data: 2 },
{ status: true, reason: 3 },
{ status: true, reason: 4 }
]
Promise.allSettled = function(promiseArray) {
if(!Array.isArray(promiseArray)) {
throw new TypeError('...')
}
return new Promise((resolve, reject) => {
let length = promiseArray.length
let resultArray = []
let count = 0
for(let i = 0; i < length; i++) {
promiseArray[i].then(data => {
resultArray[i] = { status: true, data: data }
count++
}).catch(error => {
resultArray[i] = { status: true, reason: error }
count++
}).finally(() => {
if (count === length) {
resolve(resultArray)
}
})
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 变异题
# 1. 红绿灯问题
红灯三秒,绿灯2秒,黄灯1s
查看答案
function red() {
console.log('red')
}
function green() {
console.log('green')
}
function yellow() {
console.log('yellow')
}
const light = (color, time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(color === 'red') red();
if(color === 'green') green();
if(color === 'yellow') yellow();
resolve();
}, time)
})
}
const step = () => {
light('red', 3000);
.then(light('green', 2000));
.then(light('yellow', 1000));
.then(step);
}
step();
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
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
用async/await简化:
const step = async () => {
await light('red', 3000);
await light('green', 2000);
await light('yellow', 1000);
step();
}
1
2
3
4
5
6
2
3
4
5
6
# 2. 请求图片预加载
# 加载图片封装函数loadImg
查看答案
const urlIds = [1, 2, 3, 4, 5]
const loadImg = urlId => {
const url = `xxx/${urlId}`
return new Promise(( resolve, reject ) => {
const img = new Image()
img.onerror = function() {
reject(urlId)
}
img.onload = function() {
resolve(urlId)
}
img.src = url
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 正常单个加载发送 promise
查看答案
// promise
urlIds.reduce((prevPromise, urlId) => {
return prevPromise.then(() => loadImg(urlId))
}, promise.resolve())
// async/await
const loadImage = async () => {
for(i of urlIds) {
await loadImg(i)
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 一次性发请求全部返回 Promise.all
查看答案
const promiseArray = urlIds.map(urlId => loadImg(urlId))
Promise.all(promiseArray)
.then(() => { console.log('load all') })
.then(() => { console.log('load error') })
1
2
3
4
2
3
4
# 控制最大limit并发数 Promise.race
查看答案
const loadByLimit = (urlIds, loadImg, limit) => {
const urlIdsCopy = [...urlIds]
// 如果数组长度小于并发数,直接全部发送
if (urlIdsCopy.length <= limit) {
const promiseArray = urlIds.map(urlId => loadImg(urlId))
return Promise.all(promiseArray)
}
}
// [0, limit)
const promiseArray = urlIdsCopy.splice(0, limit).map(urlId => loadImg(urlId))
// [limit, length)
urlIdsCopy.reduce((prevPromise, urlId) =>
prevPromise
.then(() => Promise.race(promiseArray))
.catch(error => { console.log(error) })
// race执行后第一个返回的id
.then(resolvedId => {
// 删除对应元素
let resolvedIdPosition = promiseArray.findIndex(id => resolvedId === id)
promiseArray.splice(resolvedIdPosition, 1)
// 新的push进去
promiseArray.push(loadImg(urlId))
})
, promise.resolve())
// 剩下的不够limit个全部执行
.then(() => Promise.all(promiseArray))
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
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
# 3. 实现一个sleep函数
sleep(5000).then(() => console.log(5000))
查看答案
function sleep(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, delay)
})
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4. 用promise实现ABCACB异步任务队列
页面上有三个按钮,分别为 A、B、C,点击各个按钮都会发送异步请求且互不影响,每次请求回来的数据都为按钮的名字。
请实现当用户依次点击 A、B、C、A、C、B 的时候,最终获取的数据为 ABCACB。
解法一:利用promise微任务队列
直接利用 promise.then 方法就能实现队列的效果
function delay(name, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(name)
}, time * 1000)
})
}
class Queue {
// 定义一个变量为promise对象
promise = Promise.resolve()
excute(promise) {
// 每次调用都在当前的then里面添加一个promise函数
this.promise = this.promise.then(() => promise)
// 返回promise
return this.promise
}
}
const queue = new Queue()
const handleClick = async (name, time) => {
// 用await函数执行queue.excute的promise得到结果
const res = await queue.excute(delay(name, time))
console.log(res)
}
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
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
解法二:自定义队列
class Queue {
constructor() {
this.queue = [];
this.running = false;
}
addTask(fn) {
this.queue.push(fn);
if (!this.running) {
this.runTask();
}
}
runTask() {
if (!this.queue.length) return;
const fn = this.queue.shift();
this.running = true;
fn.then(res => {
// 执行函数
console.log(res);
this.running = false;
this.runTask();
});
}
}
function delay(name, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(name)
}, time * 1000)
})
}
let TaskQueue = new Queue();
function handleClick(name, time) {
TaskQueue.addTask(delay(name, time))
}
handleClick('A', 1);
handleClick('B', 2);
handleClick('C', 3);
handleClick('A', 1);
handleClick('C', 3);
handleClick('B', 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
# 5. Promise.all中错误的请求重试
异步请求通过 Promise.all 处理,怎么让其中失败的所有请求重试。
Promise.all([A, B, C, D])
// 4 个请求完成后发现 AD 请求失败了,如果让 AD 请求重试
请求两次后自动停止
function request(name, count = 0) {
return new Promise((resolve, reject) => {
// 模拟接口成功与否
const isSuccess = Math.random() > 0.5;
if (!isSuccess) console.log(`接口${name}第${count+1}失败`);
// 模拟接口无顺序返回
setTimeout(() => {
isSuccess ? resolve(name) : reject(name);
}, Math.random() * 1000);
}).catch((err) => {
count++;
// 超过第二次抛出错误
if (count > 1) {
return Promise.reject(`接口${name}重新请求失败`);
}
// 未超过两次重新请求
return request(name, count);
});
}
let queue = [request('A'), request('B'), request('C'), request('D')];
Promise.all(queue)
.then((arr) => {
console.log(arr);
})
.catch((err) => {
console.log(err);
});
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
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
解法二:规定重试次数
/*
* @description 失败重试
* @param request Promise 对象
* @param times 设置的总尝试次数
* @param time 第几次
*/
function tryRequest(request, times = 0, time = 1) {
return request().catch(e => {
if (time <= times) {
console.log(e.message, `第${time}次重试`)
return tryRequest(request, times, ++time)
} else {
return Promise.reject(e)
}
})
}
// 创建模拟请求
function request(name) {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(`${name} success`)
} else {
reject(new Error(`${name} error`))
}
}, Math.random * 1000)
})
}
}
let requests = ['A', 'B', 'C', 'D'].map(item => {
return tryRequest(request(item), 3)
})
Promise.all(requests).then(res => {
console.log('成功了:', res)
}).catch(e => {
console.log('失败了:', e.message)
})
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
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