跳到主要内容

异步求和

题目描述

假设客户端不支持加法计算,服务端提供了接口 asyncAdd,用于计算两个整数的和。asyncAdd 的定义如下:

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

请实现一个异步求和函数 sum, 使得 sum(1, 2, 3).then(result => console.log(result)) 能够输出 6

示例:

sum(1, 2, 3).then(result => console.log(result)); // 6
sum(1, 2, 3, 4).then(result => console.log(result)); // 10
sum(1, 2, 3, 4, 5).then(result => console.log(result)); // 15

进阶:

你可以通过并行调用的方式来优化请求时间吗,请注意浏览器可能会有最大并行请求限制。

解法一:串行调用

思路

可以使用递归来解决这个问题。具体来说,可以将数组 arr 分为两部分,分别计算左半部分和右半部分的和,然后将两部分的和相加。

代码

回调函数实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => {
cb(a + b);
}, 1000);
}

function sum(...args: number[]): Promise<number> {
return new Promise((resolve, reject) => {
if (args.length === 0) {
resolve(0);
} else if (args.length === 1) {
resolve(arr[0]);
} else {
asyncAdd(args[0], args[1], (result) => {
sum(result, ...args.slice(2)).then(resolve);
});
}
});
}

Promise 递归实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export function sum(...args: number[]): Promise<number> {
if (args.length === 0) return Promise.resolve(0);
if (args.length === 1) return Promise.resolve(arr[0] || 0);
const [a = 0, b = 0, ...rest] = args;
return sumTwo(a, b).then((result) => {
if (rest.length === 0) return result;
return sum(result, ...rest);
});
}

async/await 递归实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export async function sum(...arr: number[]): Promise<number> {
if (arr.length === 0) return 0;
if (arr.length === 1) return arr[0] || 0;
const [a = 0, b = 0, ...rest] = arr;
const result = await sumTwo(a, b);
return rest.length === 0 ? result : sum(result, ...rest);
}

async/await 非递归实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export async function sum(...args: number[]): Promise<number> {
const results = [...args];
while (result.length > 1) {
const [a = 0, b = 0] = result.splice(0, 2);
results.push(await sumTwo(a, b));
}
return results[0] || 0;
}

解法二:并行调用

思路

将数组每两个元素分为一组,然后并行调用 asyncAdd 函数,最后将结果相加。

代码

Promise 实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export function sum(...args: number[]): Promise<number> {
if (args.length === 0) return Promise.resolve(0);
if (args.length === 1) return Promise.resolve(args[0] || 0);

const promises: Promise<number>[] = [];

for (let i = 0; i < args.length; i += 2) {
promises.push(sumTwo(args[i] || 0, args[i + 1] || 0));
}

return Promise.all(promises).then(results => sum(...results));
}

async/await 实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export async function sum(...args: number[]): Promise<number> {
if (args.length === 0) return Promise.resolve(0);
if (args.length === 1) return Promise.resolve(args[0] || 0);

const promises: Promise<number>[] = [];

for (let i = 0; i < args.length; i += 2) {
promises.push(sumTwo(args[i] || 0, args[i + 1] || 0));
}

const results = await Promise.all(promises);
return sum(...results);
}

async/await 非递归实现

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

export async function sum(...args: number[]): Promise<number> {
let results: number[] = [...args];
while (results.length > 1) {
const promises: Promise<number>[] = [];
for (let i = 0; i < results.length; i += 2) {
promises.push(sumTwo(results[i] || 0, results[i + 1] || 0));
}
results = await Promise.all(promises);
}
return results[0] || 0;
}

解法三:并行调用增加并发控制

思路

在解法二的基础上,可以通过控制并发数来优化请求时间。假设浏览器最大并行请求数为 MAX_CONCURRENCY,且该数量默认为 3

代码

function asyncAdd(a: number, b: number, cb: (result: number) => void): void {
setTimeout(() => cb(a + b), 50);
}

function sumTwo(a: number, b: number): Promise<number> {
return new Promise<number>(resolve =>
asyncAdd(a, b, result => resolve(result)),
);
}

const MAX_CONCURRENCY = 3;

export async function sum(...args: number[]): Promise<number> {
let result = [...args];
while (result.length > 1) {
const promises = result.splice(0, MAX_CONCURRENCY * 2)
.map((_, i, arr) => sumTwo(arr[i] || 0, arr[i + 1] || 0));
result = result.concat(await Promise.all(promises));
}
return args[0] || 0;
}