Looking for a way to optimize my code, I have seen that there are three ways to create asynchronous tasks:
- call back
- Promise Chaining
- async/await
What are the differences between callbacks, promises and async/await?
Looking for a way to optimize my code, I have seen that there are three ways to create asynchronous tasks:
What are the differences between callbacks, promises and async/await?
Imagine that you have to call a function that will take time to respond. But you need its answer to be able to invoke another one later, since the second one needs as a parameter what the first one has produced. For example, the first might download a Web page, extract a URL from it, and the second might need this URL to access another resource.
Using synchronous code, everything is easily understood
because we assume that the calls to
serv1()
andserv2()
are made synchronously, that is, that until these functions do not have their response, the execution will not continue through the next line. This type of code is easy to write and understand, but it is very inefficient. It is assumed that the time it takesserv1()
andserv2()
in getting your response is mostly waiting time (network communications, for example), time during which the thread is stopped waiting for the response, and that you could have used doing something else. In fact, in JavaScript, this thread is the one that deals with interacting with the DOM and with the user, so the page would remain "hung" this time. And if the time is too long, the Browser will eventually detect that the thread isn't processing GUI events and will output a warning like "A script on this page is taking too long. Do you want to abort it?"The solution is to make use of asynchronous versions of these functions. Let's call them
asinc1()
andasinc2()
These functions return immediately to the main thread, but leave the Browser "in charge" of making the corresponding requests in another thread (dedicated to network communications).The problem is what happens once those requests have been completed? Where does the answer go? Since we need the response from
asinc1()
before we can invokeasinc2()
, how does our program know when it finishedasinc1()
and what the result was?There are two possible implementations to solve this problem:
The first solution ( callbacks ) is more direct, but results in more nested code, as you can see in the following example:
Nesting would be worse as we have more chained functions (the result of one is needed to call the next), giving rise to functions that are parameters of other functions, that are parameters of others, etc. and the source code acquires a characteristic form in which it indents more and more and then undoes that indentation as braces and parentheses are closed, a structure known as Pyramid of Doom , or Callback hell .
On the other hand, in this code all the logic is "backwards" in some way, since the functions do not return results, but rather pass those results as parameters (to other functions), and the functions that handle the response are in turn passed as parameters. Error flow (which I won't go into in this answer) is also complicated, as exceptions can no longer be used, instead there would typically be two callback functions , one called on success and one called on failure.
Promises avoid this nesting, and make error handling simpler. A Promise has a method
then()
that you pass a function to, which will be executed automatically when the promise resolves (or immediately if it was already resolved at the time we invoked.then()
). This function will receive as a parameter the value of the promise (the expected result). And the best thing is that it.then()
returns a new Promise, which will be resolved when the function that we had associated with it is executed. In this way, several can be chained together.then()
to "simulate" a sequential code, like "when this promise resolves, do this, and then, when this one resolves, do this, and then...)This would be the new syntax:
Since 2017 JavaScript also has the words
async/await
, which are syntactic sugar for using Promises with a new syntax that hides them, and makes them look like synchronous code.If we use the word
await
in front of a function call, that function is understood to return a promise. Then somehow (I won't go into the messy details of how it does it "inside") execution pauses at that point (but doesn't block the main thread) and only resumes when the promise has been resolved, and thenawait
returns the value of the promise as a result.Thanks to this the code would now be:
Compare the latter with the former (synchronous). The structure is identical and has only been put
await
before asynchronous calls, andasync
before the function (since only a function declared withasync
can useawait
).The runtime efficiency of the three asynchronous solutions is similar. What changes a lot is the structure of the code, which is simplified with Promises and even more so with
await
, possibly resulting in fewer errors, and better maintainability and debugging.To answer your question you have to answer something fundamental first
What is asynchrony?
The term is used loosely in Javascript and programmers usually associate it with the ability to execute a task without blocking the Javascript thread, which is completely false . What asynchrony actually means is the ability to execute instructions in a different stage of the event cycle or event loop as it is known in English. You can perfectly schedule a job with
setTimeout
, for example, and this, when it's time to run, blocks the main thread.Don't press the button on this snippet unless you're ready to see your browser crash and I'm using a
setTimeout
to schedule the tasktareaIntensiva
that contains the loop. In any case, the browser will show you a sign that there is a script blocking the page.The event loop is composed in a very simplified way by different messages or functions that are executed one after the other following a certain pre-established order using a specific system of priorities until there are no more messages to process. In another more advanced model of abstraction it is composed of normal user code, micro-tasks and macro-tasks . This topic is quite complex but you can find here a fairly extensive and detailed article on how everything works. To explain it in a simplified way when you use
setTimeout
you create a macro-task that will be executed in some later cycle, promises create micro-tasks are executed in the same cycle you are in right after executing all the traditional code you write without these artifacts. It follows that if you callsetTimeout
and a promise the latter will be executed first.I have to explain all of the above so you understand how it applies to your problem:
Callbacks
In terms of styles they are the worst decision as they are known to create the callback hell that makes the nested code completely unreadable. Regarding error handling in Nodejs there is a convention of passing the error as the first parameter but this standard can be violated since callbacks are simple functions that you pass as parameters.
In terms of execution, callbacks are agnostic since it depends on the implementer to use micro-tasks or macro-tasks for their execution. This might not incur asynchrony at all. Here is an example:
This harmless code is only executed in a later stage of the event loop if and only if
param > 20
, otherwise it would be the same as writing for example:This code doesn't even run a task which sometimes creates real headaches trying to figure out what's wrong. In this case, the correct thing would be to use it
setTimeout
to simulate asynchrony and that the function behaves in the same way regardless of the parameters.promises
They are usually executed using micro-tasks (only in extreme cases when nothing else is available are macro-tasks used). If you are using native promises in both Node and the browser they are guaranteed to be micro-tasks so you can safely say that whatever they do, promises ARE asynchronous.
Style-wise it's an improvement over callbacks but actually both the
then
and thecatch
use callbacks to schedule code execution. The advantage is that the pyramid is not created since the promises can be chained. Among other reasons they are designed to make asynchronous code retain properties of synchronous code such as flat indentation and a single error channel which is impossible to achieve with pure callbacks.It is important to understand that you can create a chain as long as you want because every time you return something in a
then
a new promise is created (you returnundefined
a function if you don't return anything) but for each of themthen
a new micro-task is programmed that although it is less expensive than a macro-task it still has a small cost in the event loop.One aspect that many developers forget is that you should always handle the error at the end of a chain since, for example, in Node an errored promise that is not caught could kill the process . This is important to keep in mind for what I am going to say in the next point.
async/await
It's syntactic sugar on promises. For that same reason they use micro-tasks as well. There are claims that they can be optimized to be faster . This of course depends on the browser and is not 100% secure every time.
As for style "promises" less indentation than promises but if you remember what I said before about handling errors you will have to write a
try/catch
to handle the error and inevitably you will create indentation. If you use some abstraction model you can handle the error elsewhere getting completely flat and very easy to read code.There are also advantages to handling errors with async/await since any error thrown within the function
async
will be caught by the function and turned into a rejected promise automatically.That said, it is clear that you cannot compare promises and callbacks since they fulfill a different function but in terms of style and if you have truly asynchronous code (for example ajax calls, reading files, websocket connections, etc) they will be a great improvement in terms of readability is concerned. If your code is really complex, async/await will be the best option in terms of promises to write and understand your code.
For completeness you should know that there are tons of ways to create asynchrony, not just promises. You can find standard methods like
setTimeout
,setInterval
,requestAnimationFrame
,process.nextTick
,setInmediate
and informal methods like MutationObserver which allows you to artificially create a micro-task in the browser. If you want to do really intensive operations that can block the main thread you should use Web WorkersThe two previous answers, from @devconcept and @abulafia, explain the concepts in great detail, but I want to provide a version for quick references, where you can see how we can go from callbacks to promises, and with them how to use the syntax
async / await
Therefore, this answer will be a practical example where we will take an asynchronous call and refactor it step by step until we obtain the most elegant code possible.
Callbacks
Let's imagine that we have the classic AJAX call through XHR:
The AJAX call is asynchronous, which means that we know when the request (
xhr.send();
) is made but not when the response will be returned. All we know is that when the browser receives the response, the function will be calledcallbackFunction
because we have defined it that way.But now let's imagine that we want to create a function that allows us to make AJAX calls in a simple way, to have the calls made in one line:
This is progress, but we have a problem if we have dependencies: What happens if the second call depends on the first? We would be left with something like this:
En este caso aún tenemos algo manejable (3 llamadas anidadadas), pero este anidamiento nos puede causar el llamado Callback Hell (infierno de llamadas anidadas):
Promesas
¿Cómo evitarlo? Pues con promesas. Una promesa es un objeto que permite encadenar acciones asíncronas, diciendo "realiza la acción A, cuando termines A, ejecuta B, entonces ejecuta C, entonces ejecuta ..." de un modo algo más sencillo:
Como vemos, una promesa tiene el método
then
, al que se le pasa la función callback. Lo bueno de este método es que si la función callback devuelve algo, el método lo devuelve a su vez como el resultado de una nueva promesa (si la función devuelve una promesa, no se anidan, es así de inteligente), con lo que puedes tener un número indefinido de métodosthen
encadenados.async / await
De este modo ya nos hemos ahorrado una anidación exagerada, pero el código sigue siendo un poco feo. ¿Se puede hacer más elegante?
La respuesta es sí: existe una sintaxis que es equivalente a usar Promesas pero las "oculta", haciendo que el código parezca síncrono, sin necesidad de callbacks, anidadas o no:
Ejemplo de que es async y await:
Supongamos el siguiente caso hipotético, donde hacemos una petición a una API(esta API nos devuelve un array de productos) ,está por defecto, va a tener una latencia o un 'delay'.
En el caso anteriormente mencionado nosotros usamos async y await... Ahora les mostraré de como seria sin async y await.
Resumen:Async y Await nos da una sintaxis más bonita y fácil de usar...