Summary of Promises in One Sheet
Contents
- Promise status
- How to deliver response results
- Microtasks
- Continuous motion
- Promise API
- Catch Case Study
- Application Examples
Promise status
Promises have two states: pending, fulfilled, and rejected. The status can only be changed from Pending to Fulfill/Reject.
- pending : Initial state
- fulfilled : success status, resolve(), Promise.resolve()
- rejected : Failed status, reject(), Promise.reject()
The status of the fulfilled can be handled by then. The value passed through resolve is passed as an argument to then.
Promise.resolve(10)
.then(result => console.log(result)) // 10
The rejected state can be handled with a catch. The value passed through reject is passed as an argument to the catch.
Promise.reject({code: 404})
.catch(({code}) => console.log(code)) // 404
How to deliver response results
There is a difference between callback and promise in the delivery of response results.
[Promise] Active Async Control
Promises require you to call then to get the result, and you can call then when you need it to receive the data.
let result;
const promise = new Promise(r => $.post(url1, data1, r));
promise.then(v => {
result = v;
});
const promise1 = new Promise(r => $.post(url1, data1, r));
const promise2 = new Promise(r => $.post(url2, data2, r));
promise1.then(result => {
promise2.then(v => {
result.nick = v.nick;
report(result);
});
});
[Callback] Passive Async Control
You can send a callback, but you don't know when it will come.
$.post(url. data, () => {
// you don't know when it will come.
})
Realistically, the results are generated through a large number of API requests, so it matters when the response comes.
let result;
$.post(url1, data1, v => {
result = v;
});
$.post(url2, data2, v => {
result.nick = v.nick;
report(result);
});
Microtasks
Among the tasks that are registered asynchronously, there is a microtask that is executed first. Microtasks can be registered through Promise.
JavaScript Engine
The JavaScript engine basically runs on a single thread. One thread means you have one stack, and you can only do one thing at the same time. The secret lies in the event loop and queues.
Event Loops and Queues
The event loop keeps iterating and checking the work between the call stack and the queue. If the call stack is empty, the job is taken from the queue and put into the call stack.
If there is no work in the call stack, the microtask queue is checked first. If the microtask queue is empty and there are no more tasks to process, check the task queue. If you have a task in the task queue, you can take it out and put it in the call stack.
JavaScript Handling
- Tasks that are registered as asynchronous tasks are divided into Task, Microtask, and AnimationFrame tasks.
- Microtask processes tasks before Tasks.
- After the microtask is processed, requestAnimationFrame is called, followed by browser rendering.
console.log('script start')
setTimeout(() => console.log('setTimeout'), 0)
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'))
requestAnimationFrame(() => console.log('requestAnimationFrame'))
console.log('script end')
$ script start
$ script end
$ promise1
$ promise2
$ requestAnimationFrame
$ setTimeout
Continuous actions
Promises can treat asynchronous as a value. A function that is processed as a promise can perform continuous actions through the returned promise. On the other hand, functions that are handled by callbacks have no value returned and must be processed internally.
const add10 = (a, callback) => {
setTimeout(() => callback(a + 10), 100);
};
add10(10, res => {
add10(res, res => {
console.log(res);
});
});
const add20 = (a, callback) => {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
};
add20(10)
.then(add20)
.then(console.log);
Promise API
resolve/reject
const promise = new Promise((resolve, reject) => {
getData(
response => resolve(response.data),
error => reject(error.message)
)
})
then/catch
promise
.then(data => console.log(data))
.catch(err => console.error(err))
all
Promise.all will receive the result through then when all of them are in the fulfillment state.
Promise
.all([
getPromise(),
getPromise(),
getPromise()
])
//response all data
.then(data => console.log(data))
.catch(err => console.error(err))
If any of them are rejected, the catch will be executed.
Promise
.all([
Promise.resolve(1),
Promise.reject(2),
])
.catch(err => console.error(err))
// 2
race
Promise.race completes when any of them are fulfilled or rejected. When complete, it will execute either a then or a catch, depending on the status.
Promise
.race([
getPromise(), //1000ms
getPromise(), //500ms
getPromise() //250ms
])
//response of 250ms
.then(data => console.log(data))
Promise
.race([
getPromise(), //1000ms
getPromise(), //500ms
getPromiseReject() //250ms
])
//response of 250ms
.catch(err => console.error(err))
Catch Case Study
Returning the Synchronous Value from a Catch
If the catch returns a value, it will be received in the next then. Even if you get a catch when HTTP Status is not 2XX, sometimes you want to treat it as a success. It seems to be useful in this case.
const fetchData = _ => new Promise((_, reject) => {
reject(100)
})
const fetchWrapper = _ => fetchData()
.catch(() => 'fail')
fetchWrapper()
.then(response => console.log(`response ${response}`))
// response fail
Returning Promise.reject() from catch
If you return Promise.reject() from a catch, you will receive it from the catch. It seems to be easy to handle common errors.
const fetchData = _ => new Promise((_, reject) => {
reject(100)
})
const fetchWrapper = _ => fetchData()
.catch(() => Promise.reject('fail'))
fetchWrapper()
.catch(error => console.log(`error ${error}`))
// error fail
Application Examples
Asynchronous processing with minimum request time
If a response is received before 5 seconds, the request will be made again after 5 seconds, and if a response is received after 5 seconds, the request will be made again.
const recur = () => Promise.all([
new Promise(resolve => setTimeout(resolve, 5000)),
getData
]).then(recur)
async, await + Promise.all
You can use async and await to write code similar to synchronous code. You can use Promise.all to implement parallelism. There is a delay function that executes resolve after a certain period of time, as shown below.
const delay = ms => new Promise(resolve => {
setTimeout(() => resolve(ms), ms)
});
When you use a function that returns a promise, you can receive the resolve value via await. The result of the main function is returned after 6000ms.
const main = async () => {
console.time('main');
const delay1s = await delay(1000);
const delay2s = await delay(2000);
const delay3s = await delay(3000);
console.timeEnd('main');
return delay1s + delay2s + delay3s;
};
main().then(console.log);
// main: 6005.81787109375ms
// 6000
If each promise doesn't affect each other, it needs to be processed in parallel. At the end of every promise, it is checked via Promise.all. The result of the function is returned after 3000ms. If you do parallel processing, you can get a quick response.
const main = async () => {
console.time('main');
const [delay1s, delay2s, delay3s] = await Promise.all([delay(1000), delay(2000), delay(3000)]);
console.timeEnd('main');
return delay1s + delay2s + delay3s;
};
main().then(console.log);
// main: 3001.468017578125ms
// 6000