JavaScript is a single-threaded language that has many use-cases for pausing execution on it's main and lone thread while it completes other tasks, yet it is unable to spawn a new thread. The following pseudo-code would disable all interactivity e.g. button clicks, mouse & keyboard events on a web page for a second at a time.
console.log("Foo")
sleep(1)
console.log("Bar")
sleep(1)
console.log("FooBar")
sleep(1)
The problem is JavaScript does near enough everything on a site, so blocking it's only thread is troublesome.
Callbacks
A common work-around for this requirement from the day of old is to use WebAPI functions to hand off work to external implementations.
setTimeout(() => {
console.log("Foo")
setTimeout(() => {
console.log("Bar")
setTimeout(() => {
console.log("FooBar")
}, 1000)
}, 1000)
}, 1000)
How these calls to WebAPIs work outside of JavaScript's single thread involves the Event Loop, the system most important to a JavaScript engine.
- JavaScript call stack is empty, nothing left to execute
- Check Microtask queue for resolved/rejected Promise callbacks
- Check Macrotask queue for completed timers, I/O operations etc.
- If there, return the callback reference to the call stack to be executed
More depth can be reached on the Event Loop over on MDN.
Promise
A Promise can be simplified to a JavaScript object with three states, said object will have two callbacks, one for the resolved value and the other for the rejected value.
- Pending
- Fulfilled
- Rejected
const foo = somePromise()
console.log(foo)
Promise { pending }
When we await a Promise, we are blocking the current scope's execution until said Promise either resolves or rejects. The call stack can still be populated elsewhere.
async function foobar() {
const foo = await sleep(1)
console.log("bar")
}
foobar()
console.log("foo")
foo
bar
Now if I needed a value returned from the asynchronous function, I would need to wait for the Promise it returns to resolve or reject. Since we don't care about what data it creates/uses in this example we can just call it and move to the next item on the call stack.
History
- feat(javascript-promise): init