2723. 两个 Promise 对象相加
给定两个 promise 对象 promise1 和 promise2,返回一个新的 promise。promise1 和 promise2 都会被解析为一个数字。返回的 Promise 应该解析为这两个数字的和。
示例 1:
输入:
promise1 = new Promise(resolve => setTimeout(() => resolve(2), 20)),
promise2 = new Promise(resolve => setTimeout(() => resolve(5), 60))
输出:7
解释:两个输入的 Promise 分别解析为值 2 和 5。返回的 Promise 应该解析为 2 + 5 = 7。返回的 Promise 解析的时间不作为判断条件。
示例 2:
输入:
promise1 = new Promise(resolve => setTimeout(() => resolve(10), 50)),
promise2 = new Promise(resolve => setTimeout(() => resolve(-12), 30))
输出:-2
解释:两个输入的 Promise 分别解析为值 10 和 -12。返回的 Promise 应该解析为 10 + -12 = -2。
提示:
promise1 和 promise2都是被解析为一个数字的 promise 对象
思路
这题主要考察的是 Promise API。
developer.mozilla.org/zh-CN/docs/…
调用 Promise All,可以在所有的 Promise 都 fullfilled 之后返回所有返回值的数组。如果任何一个 Promise 被拒绝,则整个 Promise.All 都会执行失败。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// Expected output: Array [3, 42, "foo"]
所以这题的思路是,获得所有 Promise 执行成功的返回数组后,求结果之和即可。
代码
type P = Promise<number>
async function addTwoPromises(promise1: P, promise2: P): P {
const [res1, res2] = await Promise.all([promise1, promise2]);
return res1 + res2;
};
/**
* addTwoPromises(Promise.resolve(2), Promise.resolve(2))
* .then(console.log); // 4
*/
2621. 睡眠函数
请你编写一个异步函数,它接收一个正整数参数 millis ,并休眠 millis 毫秒。要求此函数可以解析任何值。
示例 1:
输入:millis = 100
输出:100
解释:
在 100ms 后此异步函数执行完时返回一个 Promise 对象
let t = Date.now();
sleep(100).then(() => {
console.log(Date.now() - t); // 100
});
示例 2:
输入:millis = 200
输出:200
解释:在 200ms 后函数执行完时返回一个 Promise 对象
提示:
1 <= millis <= 1000
思路
这题是经典的睡眠函数问题。这题需要理解 Promise 构造函数的传入参数。
参数是一个函数,函数的入参是 resolve 和 reject,分别控制 Promise 状态转化为 fulfilled 状态 和 rejected 状态。
示例:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
promise1.then((value) => {
console.log(value);
// Expected output: "foo"
});
console.log(promise1);
// Expected output: [object Promise]
代码
async function sleep(millis: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, millis));
}
/**
* let t = Date.now()
* sleep(100).then(() => console.log(Date.now() - t)) // 100
*/
2715. 执行可取消的延迟函数
给定一个函数 fn ,一个参数数组 args 和一个以毫秒为单位的超时时间 t ,返回一个取消函数 cancelFn 。
在 cancelTimeMs 的延迟后,返回的取消函数 cancelFn 将被调用。
setTimeout(cancelFn, cancelTimeMs)
最初,函数 fn 的执行应该延迟 t 毫秒。
如果在 t 毫秒的延迟之前调用了函数 cancelFn,它应该取消 fn 的延迟执行。否则,如果在指定的延迟 t 内没有调用 cancelFn,则应执行 fn,并使用提供的 args 作为参数。
示例 1:
输入:fn = (x) => x * 5, args = [2], t = 20
输出:[{"time": 20, "returned": 10}]
解释:
const cancelTimeMs = 50;
const cancelFn = cancellable((x) => x * 5, [2], 20);
setTimeout(cancelFn, cancelTimeMs);
取消操作被安排在延迟了 cancelTimeMs(50毫秒)后进行,这发生在 fn(2) 在20毫秒时执行之后。
示例 2:
输入:fn = (x) => x**2, args = [2], t = 100
输出:[]
解释:
const cancelTimeMs = 50;
const cancelFn = cancellable((x) => x**2, [2], 100);
setTimeout(cancelFn, cancelTimeMs);
取消操作被安排在延迟了 cancelTimeMs(50毫秒)后进行,这发生在 fn(2) 在100毫秒时执行之前,导致 fn(2) 从未被调用。
示例 3:
输入:fn = (x1, x2) => x1 * x2, args = [2,4], t = 30
输出:[{"time": 30, "returned": 8}]
解释:
const cancelTimeMs = 100;
const cancelFn = cancellable((x1, x2) => x1 * x2, [2,4], 30);
setTimeout(cancelFn, cancelTimeMs);
取消操作被安排在延迟了 cancelTimeMs(100毫秒)后进行,这发生在 fn(2,4) 在30毫秒时执行之后。
提示:
fn是一个函数args是一个有效的 JSON 数组1 <= args.length <= 1020 <= t <= 100010 <= cancelTimeMs <= 1000
思路
这题十分的出题十分的巧妙,还是需要使用闭包。
闭包里设置一个计时器,这个计时器在没有被取消的情况下, t 毫秒后执行 fn 函数。
而 cancellable 函数同样是一个工厂函数,返回一个函数(cancelFn)。这个函数依照题意,在延迟了 cancelTimeMs 毫秒后执行的时候,需要取消计时器的执行。取消计时器的方法是 clearTimeout(timerId)。
代码
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (...args: JSONValue[]) => void
function cancellable(fn: Fn, args: JSONValue[], t: number): Function {
// 声明计时器闭包:在 t 毫秒后执行 fn 函数
const timerId = setTimeout(() => fn(...args), t);
return () => {
// 返回的函数在执行的时候,立即停止 timer。提供给外部来取消闭包 timer 的执行
clearTimeout(timerId);
}
};
/**
* const result = [];
*
* const fn = (x) => x * 5;
* const args = [2], t = 20, cancelTimeMs = 50;
*
* const start = performance.now();
*
* const log = (...argsArr) => {
* const diff = Math.floor(performance.now() - start);
* result.push({"time": diff, "returned": fn(...argsArr)});
* }
*
* const cancel = cancellable(log, args, t);
*
* const maxT = Math.max(t, cancelTimeMs);
*
* setTimeout(cancel, cancelTimeMs);
*
* setTimeout(() => {
* console.log(result); // [{"time":20,"returned":10}]
* }, maxT + 15)
*/
2725. 间隔取消
现给定一个函数 fn,一个参数数组 args 和一个时间间隔 t,返回一个取消函数 cancelFn。
在经过 cancelTimeMs 毫秒的延迟后,将调用返回的取消函数 cancelFn。
setTimeout(cancelFn, cancelTimeMs)
函数 fn 应立即使用参数 args 调用,然后每隔 t 毫秒调用一次,直到在 cancelTimeMs 毫秒时调用 cancelFn。
示例 1:
输入:fn = (x) => x * 2, args = [4], t = 35, cancelT = 190
输出:
[ {"time": 0, "returned": 8}, {"time": 35, "returned": 8}, {"time": 70, "returned": 8}, {"time": 105, "returned": 8}, {"time": 140, "returned": 8}, {"time": 175, "returned": 8}]
解释:
const cancelTimeMs = 190;
const cancelFn = cancellable((x) => x * 2, [4], 35);
setTimeout(cancelFn, cancelTimeMs);
每隔 35ms,调用 fn(4)。直到 t=190ms,然后取消。
第一次调用 fn 是在 0ms。fn(4) 返回 8。
第二次调用 fn 是在 35ms。fn(4) 返回 8。
第三次调用 fn 是在 70ms。fn(4) 返回 8。
第四次调用 fn 是在 105ms。fn(4) 返回 8。
第五次调用 fn 是在 140ms。fn(4) 返回 8。
第六次调用 fn 是在 175ms。fn(4) 返回 8。
在 t=190ms 时取消
示例 2:
输入:fn = (x1, x2) => (x1 * x2), args = [2, 5], t = 30, cancelT = 165
输出:
[ {"time": 0, "returned": 10}, {"time": 30, "returned": 10}, {"time": 60, "returned": 10}, {"time": 90, "returned": 10}, {"time": 120, "returned": 10}, {"time": 150, "returned": 10}]
解释:
const cancelTimeMs = 165;
const cancelFn = cancellable((x1, x2) => (x1 * x2), [2, 5], 30)
setTimeout(cancelFn, cancelTimeMs)
每隔 30ms,调用 fn(2, 5)。直到 t=165ms,然后取消。
第一次调用 fn 是在 0ms
第二次调用 fn 是在 30ms
第三次调用 fn 是在 60ms
第四次调用 fn 是在 90ms
第五次调用 fn 是在 120ms
第六次调用 fn 是在 150ms
在 165ms 取消
示例 3:
输入:fn = (x1, x2, x3) => (x1 + x2 + x3), args = [5, 1, 3], t = 50, cancelT = 180
输出:
[ {"time": 0, "returned": 9}, {"time": 50, "returned": 9}, {"time": 100, "returned": 9}, {"time": 150, "returned": 9}]
解释:
const cancelTimeMs = 180;
const cancelFn = cancellable((x1, x2, x3) => (x1 + x2 + x3), [5, 1, 3], 50)
setTimeout(cancelFn, cancelTimeMs)
每隔 50ms,调用 fn(5, 1, 3)。直到 t=180ms,然后取消。
第一次调用 fn 是在 0ms
第二次调用 fn 是在 50ms
第三次调用 fn 是在 100ms
第四次调用 fn 是在 150ms
在 180ms 取消
提示:
fn是一个函数args是一个有效的 JSON 数组1 <= args.length <= 1030 <= t <= 10010 <= cancelT <= 500
思路
这题是上一题的加强版,只不过需要注意的是,这里是在取消计时器前需要每个一段时间 t 就执行一次 fn,所以可以直接使用 setInterval。
同时,因为记录了 0ms 下的 fn 返回结果,所以需要立马执行一次之后 fn,才使用定时器执行 fn。
代码
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (...args: JSONValue[]) => void
function cancellable(fn: Fn, args: JSONValue[], t: number): Function {
// 立马执行一次
fn(...args);
// 每隔 t ms 执行一次 fn
const timerId = setInterval(() => fn(...args), t);
return () => {
// 取消定时器
clearTimeout(timerId);
}
};
/**
* const result = [];
*
* const fn = (x) => x * 2;
* const args = [4], t = 35, cancelTimeMs = 190;
*
* const start = performance.now();
*
* const log = (...argsArr) => {
* const diff = Math.floor(performance.now() - start);
* result.push({"time": diff, "returned": fn(...argsArr)});
* }
*
* const cancel = cancellable(log, args, t);
*
* setTimeout(cancel, cancelTimeMs);
*
* setTimeout(() => {
* console.log(result); // [
* // {"time":0,"returned":8},
* // {"time":35,"returned":8},
* // {"time":70,"returned":8},
* // {"time":105,"returned":8},
* // {"time":140,"returned":8},
* // {"time":175,"returned":8}
* // ]
* }, cancelTimeMs + t + 15)
*/
2637. 有时间限制的 Promise 对象
请你编写一个函数,它接受一个异步函数 fn 和一个以毫秒为单位的时间 t。它应根据限时函数返回一个有 限时 效果的函数。函数 fn 接受提供给 限时 函数的参数。
限时 函数应遵循以下规则:
- 如果
fn在t毫秒的时间限制内完成,限时 函数应返回结果。 - 如果
fn的执行超过时间限制,限时 函数应拒绝并返回字符串"Time Limit Exceeded"。
示例 1:
输入:
fn = async (n) => {
await new Promise(res => setTimeout(res, 100));
return n * n;
}
inputs = [5]
t = 50
输出:{"rejected":"Time Limit Exceeded","time":50}
解释:
const limited = timeLimit(fn, t)
const start = performance.now()
let result;
try {
const res = await limited(...inputs)
result = {"resolved": res, "time": Math.floor(performance.now() - start)};
} catch (err) {
result = {"rejected": err, "time": Math.floor(performance.now() - start)};
}
console.log(result) // 输出结果
提供的函数设置在 100ms 后执行完成,但是设置的超时时间为 50ms,所以在 t=50ms 时拒绝因为达到了超时时间。
示例 2:
输入:
fn = async (n) => {
await new Promise(res => setTimeout(res, 100));
return n * n;
}
inputs = [5]
t = 150
输出:{"resolved":25,"time":100}
解释:
在 t=100ms 时执行 5*5=25 ,没有达到超时时间。
示例 3:
输入:
fn = async (a, b) => {
await new Promise(res => setTimeout(res, 120));
return a + b;
}
inputs = [5,10]
t = 150
输出:{"resolved":15,"time":120}
解释:
在 t=120ms 时执行 5+10=15,没有达到超时时间。
示例 4:
输入:
fn = async () => {
throw "Error";
}
inputs = []
t = 1000
输出:{"rejected":"Error","time":0}
解释:
此函数始终丢出 Error
提示:
0 <= inputs.length <= 100 <= t <= 1000fn返回一个 Promise 对象
思路一(常规写法)
依据题意,这个 timeLimit 函数是一个工厂函数,用于返回一个函数(姑且定为 limted 函数),这个函数的返回值是 Promise。所以这里写法就比较套娃了。
然后 Promise 的执行规则是,如果执行超过时间限制,Promise 就直接 reject;没有超过时间限制的话,fn 是一个异步函数,所以按照正常的执行异步函数的方式去处理。
代码一
type Fn = (...params: any[]) => Promise<any>;
function timeLimit(fn: Fn, t: number): Fn {
return async function (...args) {
// 执行函数后返回一个 Promise
return new Promise((resolve, reject) => {
// 使用计时器,一旦超时就直接 reject
setTimeout(() => reject('Time Limit Exceeded'), t);
// fn 异步函数正常执行的情况,传递 resolve 和 reject
fn(...args)
.then(resolve)
.catch(reject);
});
};
}
/**
* const limited = timeLimit((t) => new Promise(res => setTimeout(res, t)), 100);
* limited(150).catch(console.log) // "Time Limit Exceeded" at t=100ms
*/
思路二(Promise.race,更有助于理解)
Promise.race 函数,直接返回最快的敲定 Promise 状态的对应 Promise 的返回值。换句话说,如果第一个敲定的 promise 被兑现,那么返回的 promise 也会被兑现;如果第一个敲定的 promise 被拒绝,那么返回的 promise 也会被拒绝。可以理解为所有的 Promise 赛跑,谁先返回结果就返回谁的结果,不论是 fulfilled 还是 rejected。
示例:
const promise1 = new Promise((resolve, reject) => {
// 意思为,定时器 500ms 后,调用 resolve 方法,并传递 'one'
// 第三个及往后的函数为回调参数,在定时器执行后吧 'one' 传给 resolve
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// Expected output: "two"
developer.mozilla.org/zh-CN/docs/…
虽然这题可不用 Promise.race,但题意确实是计时器和 fn 函数在赛跑,可读性会更好。
代码二
type Fn = (...params: any[]) => Promise<any>;
function timeLimit(fn: Fn, t: number): Fn {
return async function (...args) {
return Promise.race([
// 正常执行函数的 Promise
fn(...args),
// 超时 reject 的 Promise
new Promise((_, reject) =>
setTimeout(() => reject('Time Limit Exceeded'), t)
),
]);
};
}
/**
* const limited = timeLimit((t) => new Promise(res => setTimeout(res, t)), 100);
* limited(150).catch(console.log) // "Time Limit Exceeded" at t=100ms
*/
2622. 有时间限制的缓存
编写一个类,它允许获取和设置键-值对,并且每个键都有一个 过期时间 。
该类有三个公共方法:
set(key, value, duration) :接收参数为整型键 key 、整型值 value 和以毫秒为单位的持续时间 duration 。一旦 duration 到期后,这个键就无法访问。如果相同的未过期键已经存在,该方法将返回 true ,否则返回 false 。如果该键已经存在,则它的值和持续时间都应该被覆盖。
get(key) :如果存在一个未过期的键,它应该返回这个键相关的值。否则返回 -1 。
count() :返回未过期键的总数。
示例 1:
输入:
actions = ["TimeLimitedCache", "set", "get", "count", "get"]
values = [[], [1, 42, 100], [1], [], [1]]
timeDeays = [0, 0, 50, 50, 150]
输出: [null, false, 42, 1, -1]
解释:
在 t=0 时,缓存被构造。
在 t=0 时,添加一个键值对 (1: 42) ,过期时间为 100ms 。因为该值不存在,因此返回false。
在 t=50 时,请求 key=1 并返回值 42。
在 t=50 时,调用 count() ,缓存中有一个未过期的键。
在 t=100 时,key=1 到期。
在 t=150 时,调用 get(1) ,返回 -1,因为缓存是空的。
示例 2:
输入:
actions = ["TimeLimitedCache", "set", "set", "get", "get", "get", "count"]
values = [[], [1, 42, 50], [1, 50, 100], [1], [1], [1], []]
timeDelays = [0, 0, 40, 50, 120, 200, 250]
输出: [null, false, true, 50, 50, -1]
解释:
在 t=0 时,缓存被构造。
在 t=0 时,添加一个键值对 (1: 42) ,过期时间为 50ms。因为该值不存在,因此返回false。
当 t=40 时,添加一个键值对 (1: 50) ,过期时间为 100ms。因为一个未过期的键已经存在,返回 true 并覆盖这个键的旧值。
在 t=50 时,调用 get(1) ,返回 50。
在 t=120 时,调用 get(1) ,返回 50。
在 t=140 时,key=1 过期。
在 t=200 时,调用 get(1) ,但缓存为空,因此返回 -1。
在 t=250 时,count() 返回0 ,因为缓存是空的,没有未过期的键。
提示:
0 <= key, value <= 10``90 <= duration <= 10001 <= actions.length <= 100actions.length === values.lengthactions.length === timeDelays.length0 <= timeDelays[i] <= 1450actions[i]是 "TimeLimitedCache"、"set"、"get" 和 "count" 中的一个。- 第一个操作始终是 "TimeLimitedCache" 而且一定会以 0 毫秒的延迟立即执行
思路
这种涉及了缓存的东西,用 Map 数据结构基本是最简洁高效的。
这里有缓存失效的机制,所以需要一个 Map 来额外记录计时器。这些计时器用来记录哪些键值对在经过多久时间后删除。如果有键值对需要更新,就将键对应的计时器删除。
代码
class TimeLimitedCache {
private map: Map<number, number>;
private timerMap: Map<number, ReturnType<typeof setTimeout>>;
constructor() {
this.map = new Map();
this.timerMap = new Map();
}
set(key: number, value: number, duration: number): boolean {
// 判断当前是否有未过时缓存,作为返回值
const hasKey = this.map.has(key);
// 更新缓存时间:清除计时器
if (hasKey) {
clearTimeout(this.timerMap.get(key));
}
// 设置一个计时器,用来定时清除过时缓存
const timerId = setTimeout(() => {
this.map.delete(key);
}, duration);
// 更新两个 Map
this.timerMap.set(key, timerId);
this.map.set(key, value);
return hasKey;
}
get(key: number): number {
return this.map.get(key) ?? -1;
}
count(): number {
return this.map.size;
}
}
/**
* const timeLimitedCache = new TimeLimitedCache()
* timeLimitedCache.set(1, 42, 1000); // false
* timeLimitedCache.get(1) // 42
* timeLimitedCache.count() // 1
*/
2627. 函数防抖
请你编写一个函数,接收参数为另一个函数和一个以毫秒为单位的时间 t ,并返回该函数的 函数防抖 后的结果。
函数防抖 方法是一个函数,它的执行被延迟了 t 毫秒,如果在这个时间窗口内再次调用它,它的执行将被取消。你编写的防抖函数也应该接收传递的参数。
例如,假设 t = 50ms ,函数分别在 30ms 、 60ms 和 100ms 时调用。前两个函数调用将被取消,第三个函数调用将在 150ms 执行。如果改为 t = 35ms ,则第一个调用将被取消,第二个调用将在 95ms 执行,第三个调用将在 135ms 执行。
上图展示了了防抖函数是如何转换事件的。其中,每个矩形表示 100ms,反弹时间为 400ms。每种颜色代表一组不同的输入。
请在不使用 lodash 的 _.debounce() 函数的前提下解决该问题。
示例 1:
输入:
t = 50
calls = [ {"t": 50, inputs: [1]},
{"t": 75, inputs: [2]}
]
输出:[{"t": 125, inputs: [2]}]
解释:
let start = Date.now();
function log(...inputs) {
console.log([Date.now() - start, inputs ])
}
const dlog = debounce(log, 50);
setTimeout(() => dlog(1), 50);
setTimeout(() => dlog(2), 75);
第一次调用被第二次调用取消,因为第二次调用发生在 100ms 之前
第二次调用延迟 50ms,在 125ms 执行。输入为 (2)。
示例 2:
输入:
t = 20
calls = [
{"t": 50, inputs: [1]},
{"t": 100, inputs: [2]}
]
输出:[{"t": 70, inputs: [1]}, {"t": 120, inputs: [2]}]
解释:
第一次调用延迟到 70ms。输入为 (1)。
第二次调用延迟到 120ms。输入为 (2)。
示例 3:
输入:
t = 150
calls = [
{"t": 50, inputs: [1, 2]},
{"t": 300, inputs: [3, 4]},
{"t": 300, inputs: [5, 6]}
]
输出:[{"t": 200, inputs: [1,2]}, {"t": 450, inputs: [5, 6]}]
解释:
第一次调用延迟了 150ms,运行时间为 200ms。输入为 (1, 2)。
第二次调用被第三次调用取消
第三次调用延迟了 150ms,运行时间为 450ms。输入为 (5, 6)。
提示:
0 <= t <= 10001 <= calls.length <= 100 <= calls[i].t <= 10000 <= calls[i].inputs.length <= 10
思路
函数防抖是一道很经典的面试题。
在给定的时间内,如果重新执行了防抖函数,那么之前的执行会被取消(取消定时器),并重新刷新定时。
代码
type F = (...args: number[]) => void;
function debounce(fn: F, t: number): F {
let timerId: ReturnType<typeof setTimeout>;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), t);
};
}
/**
* const log = debounce(console.log, 100);
* log('Hello'); // cancelled
* log('Hello'); // cancelled
* log('Hello'); // Logged at t=100ms
*/
2721. 并行执行异步函数
给定一个异步函数数组 functions,返回一个新的 promise 对象 promise。数组中的每个函数都不接受参数并返回一个 promise。所有的 promise 都应该并行执行。
promise resolve 条件:
- 当所有从
functions返回的 promise 都成功的并行解析时。promise的解析值应该是一个按照它们在functions中的顺序排列的 promise 的解析值数组。promise应该在数组中的所有异步函数并行执行完成时解析。
promise reject 条件:
- 当任何从
functions返回的 promise 被拒绝时。promise也会被拒绝,并返回第一个拒绝的原因。
请在不使用内置的 Promise.all 函数的情况下解决。
示例 1:
输入:functions = [
() => new Promise(resolve => setTimeout(() => resolve(5), 200))
]
输出:{"t": 200, "resolved": [5]}
解释:
promiseAll(functions).then(console.log); // [5]
单个函数在 200 毫秒后以值 5 成功解析。
示例 2:
输入:functions = [
() => new Promise(resolve => setTimeout(() => resolve(1), 200)),
() => new Promise((resolve, reject) => setTimeout(() => reject("Error"), 100))
]
输出:{"t": 100, "rejected": "Error"}
解释:由于其中一个 promise 被拒绝,返回的 promise 也在同一时间被拒绝并返回相同的错误。
示例 3:
输入:functions = [
() => new Promise(resolve => setTimeout(() => resolve(4), 50)),
() => new Promise(resolve => setTimeout(() => resolve(10), 150)),
() => new Promise(resolve => setTimeout(() => resolve(16), 100))
]
输出:{"t": 150, "resolved": [4, 10, 16]}
解释:所有的 promise 都成功执行。当最后一个 promise 被解析时,返回的 promise 也被解析了。
提示:
- 函数
functions是一个返回 promise 的函数数组 1 <= functions.length <= 10
思路
这题是模拟 Promise.all 的执行。
- Promise.all 的返回值是 Promise,所以以
return new Promise起手; - 遍历所有传入的 functions,并挨个执行;
- 在正常执行时候(then 情况):判断下所有的 function 是否已经 resolved,如果是,resolve 返回数组;
- 在执行错误的时候(catch 情况):直接 reject 报错。
错误代码
type Fn<T> = () => Promise<T>;
function promiseAll<T>(functions: Fn<T>[]): Promise<T[]> {
return new Promise((resolve, reject) => {
const results = new Array(functions.length);
for (let i = 0; i < functions.length; i++) {
functions[i]()
.then((res) => (results[i] = res))
.catch((err) => reject(err));
}
resolve(results);
});
}
/**
* const promise = promiseAll([() => new Promise(res => res(42))])
* promise.then(console.log); // [42]
*/
这是典型的同步思维。这将会在所有的异步函数未执行完的时候,直接返回初始定义的空数组。
所以要在每次 then 的时候去判断所有的异步函数是否已经执行完毕才是正确解法。
代码
type Fn<T> = () => Promise<T>;
function promiseAll<T>(functions: Fn<T>[]): Promise<T[]> {
return new Promise((resolve, reject) => {
// 数组用于记录异步函数 fulfilled 返回值
const results = new Array(functions.length);
// 记录已经是 fulfilled 状态的 Promise 数量
let fulfilledCount = 0;
// 遍历执行异步函数
for (let i = 0; i < functions.length; i++) {
functions[i]()
.then((res) => {
results[i] = res;
fulfilledCount++;
// 如果都已经 fulfilled,将数组 resolve
if (fulfilledCount === functions.length) {
resolve(results);
}
})
// 如果错误,直接 reject
.catch((err) => reject(err));
}
});
}
/**
* const promise = promiseAll([() => new Promise(res => res(42))])
* promise.then(console.log); // [42]
*/