Promise 다양한 응용

Promise의 성질을 이용하여 다양한 응용을 할 수 있다.

시간초과 추가하기

1
2
3
4
5
6
7
8
9
10
const awaitTimeout = (delay) =>
new Promise((resolve) => setTimeout(resolve, delay));

awaitTimeout(300).then(() => console.log("Hi"));
// 300ms 뒤에 Hi가 출력됨

const f = async () => {
await awaitTimeout(300);
console.log("Hi");
};

위의 코드는 delay를 받아서 delay 뒤에 resolve 하는 Promise로 래핑한 함수이다.

Promise.race를 이용하여 시간초과 로직을 추가할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const awaitTimeout = (delay, reason) =>
new Promise(
(resolve, reject) =>
setTimeout(() => (reason === undefined ? resolve() : reject(reason))),
delay
);

const wrapPromise = (promise, delay, reason) =>
Promise.race([promise, awaitTimeout(delay, reason)]);

wrapPromise(fetch("https://cool.api.io/data.json"), 3000, {
reason: "Fetch timeout",
})
.then((data) => {
console.log(data.message);
})
.catch((data) => console.log(`Failed with reason: ${data.reason}`));

promise.race는 가장 먼저 resolve된 데이터만을 사용하는 함수이다.

이러한 성질을 이용하여 delay안에 promise가 resolve되지 못할 시 에러를 발생시킨다.

시간초과를 위한 함수들을 하나의 객체로 정의하였다.

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
class Timeout {
constructor() {
this.ids = [];
}

set = (delay, reason) =>
new Promise((resolve, reject) => {
const id = setTimeout(() => {
if (reason === undefined) resolve();
else reject(reason);
this.clear(id);
}, delay);
this.ids.push(id);
});

wrap = (promise, delay, reason) =>
Promise.race([promise, this.set(delay, reason)]);

clear = (...ids) => {
this.ids = this.ids.filter((id) => {
if (ids.includes(id)) {
clearTimeout(id);
return false;
}
return true;
});
};
}

const myFunc = async () => {
const timeout = new Timeout();
const timeout2 = new Timeout();
timeout.set(6000).then(() => console.log("Hello"));
timeout2.set(4000).then(() => console.log("Hi"));
timeout
.wrap(fetch("https://cool.api.io/data.json"), 3000, {
reason: "Fetch timeout",
})
.then((data) => {
console.log(data.message);
})
.catch((data) => console.log(`Failed with reason: ${data.reason}`))
.finally(() => timeout.clear(...timeout.ids));
};

더욱 더 독립적으로 사용할 수 있게 되었다.

디바운싱

클로저와 Promise를 이용하여 디바운싱을 구현할 수 있다.

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
const debouncePromise = (fn, ms = 0) => {
let timeoutId;
const pending = [];
return (...args) =>
new Promise((res, rej) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
const currentPending = [...pending];
pending.length = 0;
Promise.resolve(fn.apply(this, args)).then(
(data) => {
currentPending.forEach(({ resolve }) => resolve(data));
},
(error) => {
currentPending.forEach(({ reject }) => reject(error));
}
);
}, ms);
pending.push({ resolve: res, reject: rej });
});
};

const fn = (arg) =>
new Promise((resolve) => {
setTimeout(resolve, 1000, ["resolved", arg]);
});
const debounced = debouncePromise(fn, 200);
debounced("foo").then(console.log);
debounced("bar").then(console.log);

위 코드는 ms 만큼의 delay 후에 마지막 호출의 값을 반환한다. 이전에 호출된 함수들은 마지막 호출과 동일한 데이터를 반환한다.

호출될 경우 외부 pending 배열을 복사한 후 이를 초기화한다.

ms동안 새로운 호출이 없을 경우 복사한 pending 배열을 순회하며 resolve나 reject를 호출한다.

Ref

How can I add a timeout to a promise in JavaScript?

Debounce promise

Promise

UsingPromise

debounce function implemented with promises

댓글