# 请求封装成Promise
# then可以接受失败回调吗
可以,它接收成功和失败的回调。
Promise.resolve().then(onfulfilled?: (value: any) => any, onrejected?: (reason: any) => PromiseLike<never>)
1
# 异步加载图片
/**
* 图片异步加载
* @param { string } url
* @returns {Promise<HTMLImageElement>}
*/
function loadImageAsync(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function () {
reject(new Error(`Could not load image at: ${url}`));
};
image.src = url;
});
}
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
实现一个有并发限制的请求,以最快速度请求所有资源。需要先并发请求3张图片,当一张图片加载完成后,又会继续发起一张图片的请求,让并发数保持在3个,直到需要加载的图片都全部发起请求。
/**
* 请求控制
* @param {string[]} urls 请求地址
* @param {(url: string) => Promise<string>} handler 请求执行函数
* @param {number} limit 最大请求数量
* @returns {Promise<number[]>}
*/
function limitLoad(urls, handler, limit = 3) {
// 对数组做一个拷贝
const sequence = [...urls];
// 并发请求到最大数
// 这里返回的 index 是任务在 promises 的脚标,用于在 Promise.race 之后找到完成的任务脚标
const promises = sequence.splice(0, limit).map((url, index) => handler(url).then(() => index));
// 利用数组的 reduce 方法来以队列的形式执行
// 返回最快改变状态的 Promise 每一个promise的then都是在上一个race之后调用
return sequence.reduce((prev, cur, currentIndex) => prev.then(() => Promise.race(promises)).catch((err) => {
// 这里的 catch 不仅用来捕获前面 then 方法抛出的错误 更重要的是防止中断整个链式调用
console.error(err);
}).then((res) => {
// 用新的 Promise 替换掉最快改变状态的 Promise
promises[res] = handler(sequence[currentIndex]).then(() => res);
// 初始值Promise.resolve()开始链式 最后的.then(() => Promise.all(promises)) 是为了全部执行完成后的链式调用
}), Promise.resolve()).then(() => Promise.all(promises));
}
// 因为limitLoad函数也返回一个Promise,所以当所有图片加载完成后,可以继续链式调用
const images = [
'https://picsum.photos/200/300?1',
'https://picsum.photos/200/300?2',
'https://picsum.photos/200/300?3',
// 'https://picsum.photos_error/200/300?4', // error src
'https://picsum.photos/200/300?5',
'https://picsum.photos/200/300?6',
'https://picsum.photos/200/300?7',
]
limitLoad(images, loadImageAsync, 3).then(() => {
console.log('所有图片加载完成');
}).catch((err) => {
console.error('图片未能全部加载', 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
30
31
32
33
34
35
36
37
38
39
40
41
42
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
另一种实现方式:
function limitLoad(urls, handler, limit = 3) {
return new Promise((resolve, reject) => {
const result = [];
let count = 0;
let index = 0;
async function run() {
const curIndex = index;
const url = urls[index];
index++;
try {
const res = await handler(url);
result[curIndex] = res;
} catch (e) {
result[curIndex] = null;
}
count++;
if (count === urls.length) {
resolve(result);
}
if (index < urls.length) {
run();
}
}
for (let i = 0; i < Math.min(limit, urls.length); i++) {
run();
}
});
}
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
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
# 消除异步传染性
let load = (num) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num);
}, 2000);
});
const f0 = () => {
console.log('f0');
const res1 = load(111);
const res2 = load(222);
console.log('res1', res1);
console.log('res2', res2);
return res2;
};
const f1 = () => {
console.log('f1');
return f0();
};
const f2 = () => {
console.log('f2');
return f1();
};
const main = () => {
console.log('main');
const res = f2();
console.log(res);
};
function run(fn) {
const originLoad = load;
const cache = [];
let i = 0;
load = (num) => {
if (cache[i]) {
const cacheData = cache[i];
i++;
if (cacheData.status === 'fulfilled') {
return cacheData.data;
}
if (cacheData.status === 'rejected') {
throw cacheData.err;
}
} else {
const result = {
status: 'pending',
data: null,
err: null,
};
cache[i] = result;
throw originLoad(num).then((res) => {
result.status = 'fulfilled';
result.data = res;
}).catch((err) => {
result.status = 'rejected';
result.err = err;
});
}
};
const execute = () => {
try {
i = 0;
fn();
} catch (error) {
if (error instanceof Promise) {
error.then(execute, execute);
} else {
throw error;
}
}
};
execute();
}
run(main); 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
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
代码整体思路
函数中有异步操作,使用
throw中断函数后续的执行。异步操作执行完毕时,缓存异步结果(一个异步操作对应一个缓存)。
重新执行函数,异步操作直接返回缓存内容。
- 怎么重新执行函数?(使用
try...catch捕获错误,就可以在catch重新执行函数)。 - 什么时机重新执行函数?(当异步有结果后,即
Promise改变状态后,即可重新执行)。
- 怎么重新执行函数?(使用
优缺点
- 优点:编写代码时直接编写同步代码即可,不需要使用
async、await等。 - 缺点:函数需要多次重复执行,
async、await只需要执行一次。假如函数有其他大量计算,将影响性能。 - 共同点:仍然是异步有结果后,才能真正进行下一步操作。
# 实现请求队列
页面上有三个按钮,分别为A、B、C,点击各个按钮都会发送异步请求且互不影响,每次请求回来的数据都为按钮的名字。请实现当用户依次点击A、B、C、A、C、B的时候,最终获取的数据为ABCACB。请求不能阻塞,但是输出可以阻塞。比如说B请求需要耗时3秒,其他请求耗时1秒,那么当用户点击BAC时,三个请求都应该发起,但是因为B请求回来的慢,所以得等着输出结果。
class Queue {
promise = Promise.resolve();
execute(promise) {
this.promise = this.promise.then(() => promise);
return this.promise;
}
}
const queue = new Queue();
const delay = (params) => {
const time = Math.floor(Math.random() * 5);
return new Promise((resolve) => {
setTimeout(() => {
resolve(params);
}, time * 500);
});
};
const handleClick = async (name) => {
const res = await queue.execute(delay(name));
console.log(res);
};
handleClick('A');
handleClick('B');
handleClick('C');
handleClick('A');
handleClick('C');
handleClick('B');
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