|
There are two options for doing things in JavaScript: 1) do it now, or 2) start it now and get back to me when you're done. JavaScript makes (1) the default. For any kind of object access (dog.legs[1].paw), assignment (const q = 5), math (Math.floor(4.2)), or DOM access in a browser (document.write('yay')), the interpreter will run each statement in sequence, completing each one before running the next. Historically, the way ask the interpreter to "get back to me when you're done" was to use the "callback pattern," which was little more than passing in a function: setTimeout(4000, () => console.log('second'));
setTimeout(5000, () => console.log('third'));
setTimeout(3000, () => console.log('first'));
When it came time to construct sequential-looking programs that involved lots of these calls, things could quickly get messy: http.get(catalogUrl, (err, catalog) => {
if(err) {
handleError(err);
}
http.get(catalog.products[0].url, (err2, product) => {
if(err) {
handleError(err);
}
document.write(product.name);
});
});
Promises flipped this callback style into an object. Async/await morphed those promise objects and methods into language syntax that looks like one thing happening after another: catalog = await fetch(catalogUrl).catch(handleError)
product = await fetch(catalog.products[0].url).catch(handleError)
document.write(product.name)
If the original question were "why are some APIs async and some not?" the answer would be: "JavaScript engine authors (web browsers, node) have decided that some things like network access are inherently too slow to pretend they happen instantly, and waiting on them would mean that the UI would have to become unresponsive, which would make users think something is broken. Other APIs like DOM access are fast enough to get away with freezing the UI and nobody notices."But the question was, "why can't you call an async function from a synchronous (non-async) function?" The answer there is more like, "Because synchronous functions are expected to return a result without waiting, and if you could call an async function but pretend you didn't, there's not a good solution for what should happen in composition with other non-async functions. Would you proceed without having a result? Would you force a wait anyways? None of those are acceptable answers because they break the model of 1) some things are nearly instantaneous and 2) some things take longer. We still need a mechanism to work with things that take a long or unknown amount of time, without making the UI freeze." As lolinder points out in a sibling comment, this decision to split things into 1) fast and 2) not fast was ultimately a semantic decision of JavaScript engine/API designers. The downside of this decision is that it takes programmers some time to be comfortable with the unintuitive model. The upside is that it enables programs to be written by (fallible, mortal) programmers and not freeze up all the time. |