最近有同学面试时被问到,如果并行执行多个异步操作,该如何操作。
这个问题很简单,当然可以使用Promise.all()
的静态方法。Promise.all()
会并行执行多个异步操作时等待所有操作完成,并在所有操作完成后返回结果。
基本操作如下(该代码在React环境下运行)
import axios from 'axios';
import { useEffect } from 'react';
export default function TestPromise() {
let executePromiseAll = async () => {
let pending = [];
for (let i = 1; i <= 10; i++) {
//pending数组放入10个都是pending状态的promise
//这里读取的远程数据
pending.push(axios.get("/api/users/get/" + i));
}
//promise的三个状态 pending fulfilled rejected
//Promise.all静态方法
Promise.all(pending).then(res => {
console.log(res);
})
}
useEffect(() => {
executePromiseAll();
},[])
return (
<div>
<h2>
测试promise.all
</h2>
</div>
)
}
但是面试官继续发问,如何自己去实现一个Promise.all()
函数呢?这个时候同学就卡壳了...一问到稍微底层的就有点虚,加上面试紧张,很容易不知道该从何说起。
其实这个问题要实现很简单,
首先你要知道Promise无非就三种状态,pending,fulfilled和rejected
Promise.all()
需要的参数是一个全部是pending
状态promise数组,最后Promise.all()
执行,将所有结果返回成一个数组。如果有一个失败,则全部失败。
那问题就很简单了啊,如果我们自己实现Promise.all()
的话,只需要写一个函数,传一个全部pending状态的数组,迭代数组中的每个元素并执行,将返回结果放入到数组中即可。
let promiseAll = (promises) => {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
resolve([]);
return;
}
let result = [];
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then((value) => {
result[index] = value;
count++;
if (count === promises.length) {
resolve(result);
}
}).catch((error) => {
reject(error);
});
});
});
}
这个自定义的函数用起来,和Promise.all()
就是一模一样的
//自定义promise.all函数
promiseAll(pending).then(res => {
console.log(res);
})
这个实现实际上是很简单的,但是由此我想到了另外一个比较复杂的面试题的:
要求写一个函数,传入一个url地址的数组和number值,能够根据number值实现并发执行的效果,并将执行的结果放入到一个数组中
首先你要读懂题意,简单来说,比如有10个同时执行的异步操作,执行这个函数,传入的number值如果是3,那么就3个一组,3个一组的执行异步操作。类似于下面的效果:
要完成这个题目,首先要知道,明白一个道理,比如_request()
是一个异步执行函数,如果我们连续执行三次_request()
,是一个什么结果
_request();
_request();
_request();
如图,3个异步操作,调用的是一个接口,只是传递的值一样,这样,你看到的效果其实接近于同步效果。那也就是说,如果我们像这样同时调用异步函数,无论你怎么调用,都达不到上面的那种效果。
但是,如果在异步调用函数里面,当执行完结果后,再次执行自身递归,那就可以达到先执行完一个异步,再执行下一个的目的。伪代码如下:
async function _request(){
//...其他相关代码省略
let resp = await axios.get('xxx');
//...
_request();
}
大家都知道await是语法糖,所以,放在await后面的,肯定是当前异步函数执行之后的操作,因此,如果_request()
是像上面的递归函数,那么执行的效果,就如下图:
了解这个重点之后,就可以直接上代码了
/**
* 并发请求
* @param {Array} urls 请求的url数组
* @param {Number} max 同时并发的数量,默认值3
* @returns {Array} result 并发请求后,返回所有请求的结果
*/
let concurrency = (urls, max=3) => {
return new Promise((resolve, reject) => {
if (urls.length === 0) {
resolve([]);
return;
}
let result = []; //记录最终的结果
let count = 0;
let index = 0;
async function _request() {
if (index === urls.length) {
return;
}
//设定临时变量i
//因为是异步调用,最后把结果放入到result数组中
//所以就会存在一个问题,放入到数组中的顺序不能确定先后
//因此临时变量i就是用来确定放入到数组中的顺序
let i = index;
let url = urls[index];
index++;
console.log(url);
try {
let res = await axios.get(url);
// 不能使用push,要保证异步执行后,放入到数组中的顺序是正确的
// result.push(res);
result[i] = res;
} catch (e) {
result[i] = e;
} finally {
count++;
if (count === urls.length) {
console.log('请求全部执行完毕');
resolve(result);
}
_request(); //回调执行_request()函数
}
}
//同时执行的promise的数量不能大于数组的数量
let times = Math.min(max, urls.length);
for (let i = 0; i < times; i++) {
_request();
}
})
}
调用并发:
let urls = [];
for (let i = 1; i <= 10; i++) {
urls.push("/api/users/get/" + i);
}
concurrency(urls, 3).then(resp=>console.log(resp));
完整案例:
import axios from 'axios';
import { useEffect } from 'react';
export default function TestPromise() {
let executePromiseAll = async () => {
// 放入pending状态数组
// let pending = [];
// for (let i = 1; i <= 10; i++) {
// //pending数组放入10个都是pending状态的promise
// pending.push(axios.get("/api/users/get/" + i));
// }
// Promise.all静态方法
// Promise.all(pending).then(res => {
// console.log(res);
// })
// 自定义promise.all函数
// promiseAll(pending).then(res => {
// console.log(res);
// })
//并发请求
let urls = [];
for (let i = 1; i <= 10; i++) {
urls.push("/api/users/get/" + i);
}
concurrency(urls, 3).then(resp=>console.log(resp));
}
//自定义promise.all函数
//promise.all函数的关键点在于,当所有的promise都执行完毕后,才会执行resolve
//并将所有执行结果返回到一个数组中
//所以我们需要一个计数器,当计数器的值等于promises的长度时,说明所有的promise都执行完毕了
//这时候就可以执行resolve了
//同时,我们还需要一个数组,用来存放每个promise的执行结果
//当每个promise执行完毕后,就将执行结果存放到数组中
let promiseAll = (promises) => {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
resolve([]);
return;
}
let result = [];
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then((value) => {
result[index] = value;
count++;
if (count === promises.length) {
resolve(result);
}
}).catch((error) => {
reject(error);
});
});
});
}
/**
* 并发请求
* @param {Array} urls 请求的url数组
* @param {Number} max 同时并发的数量,默认值3
* @returns {Array} result 并发请求后,返回所有请求的结果
*/
let concurrency = (urls, max=3) => {
return new Promise((resolve, reject) => {
if (urls.length === 0) {
resolve([]);
return;
}
let result = []; //记录最终的结果
let count = 0;
let index = 0;
async function _request() {
if (index === urls.length) {
return;
}
//设定临时变量i
//因为是异步调用,最后把结果放入到result数组中
//所以就会存在一个问题,放入到数组中的顺序不能确定先后
//因此临时变量i就是用来确定放入到数组中的顺序
let i = index;
let url = urls[index];
index++;
console.log(url);
try {
let res = await axios.get(url);
// 不能使用push,要保证异步执行后,放入到数组中的顺序是正确的
// result.push(res);
result[i] = res;
} catch (e) {
result[i] = e;
} finally {
count++;
if (count === urls.length) {
console.log('请求全部执行完毕');
resolve(result);
}
_request(); //回调执行_request()函数
}
}
//同时执行的promise的数量不能大于数组的数量
let times = Math.min(max, urls.length);
for (let i = 0; i < times; i++) {
_request();
}
})
}
useEffect(() => {
executePromiseAll();
},[])
return (
<div>
<h2>
测试promise.all
</h2>
</div>
)
}
Comments