Async/Await for callback users

I have a friend that writes node.js code with callbacks. There are some cases where I think using promises or async/await syntax might be advantageous. Here, I’m going to try to connect the dots.

Here is an example of an asynchronous function that uses callbacks:

<span class="token keyword">function</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token parameter">message<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> message <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">callback</span> <span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">callback</span> <span class="token punctuation">(</span><span class="token string">'not a string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  
<span class="token keyword">function</span> <span class="token function">handleMessage</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  
<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">,</span> handleMessage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// hello</span>
<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> handleMessage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// not a string</span>

We follow the standard format of a callback with the first param err and the second param the value. If the message is not a string it displays an error message, otherwise, it displays the message. This uses setTimeout to make things asynchronous.

Next, we’re going to rewrite this code to use a slightly different syntax and split the callback into two functions.

<span class="token keyword">function</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token parameter">message<span class="token punctuation">,</span> resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> message <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">resolve</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">reject</span> <span class="token punctuation">(</span><span class="token string">'not a string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">handleMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">handleError</span> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">,</span> handleMessage<span class="token punctuation">,</span> handleError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// hello</span>
<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> handleMessage<span class="token punctuation">,</span> handleError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// not a string</span>

We no longer need to have the if statement in handleMessage because it’s only called on success. On errors we call handleError.

Now, we’ll take things one step further by replacing this callback with a Promise. This encapsulates the callbacks in an object that can be returned by our asynchronous function instead of being passed as parameters.

<span class="token keyword">function</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> message <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">resolve</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token function">reject</span> <span class="token punctuation">(</span><span class="token string">'not a string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">handleMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">handleError</span> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span> <span class="token punctuation">(</span>handleMessage<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span> <span class="token punctuation">(</span>handleError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// hello</span>
<span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span> <span class="token punctuation">(</span>handleMessage<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span> <span class="token punctuation">(</span>handleError<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// not a string</span>

One thing you can do with promises is chain promises together. If handleMessage returned a promise we could have multiple then calls, each with the next step of execution. I’m not going to overcomplicate this example with that use case, but it’s important to understand that this is desirable to avoid "callback hell" with nested callbacks. Doing this allows you to flatten the logic and break out of the flow and not continue if an error occurs.

The final step will be to convert this to use async/await syntax. Because, we’re using setTimeout which requires a callback, we won’t actually rewrite timedMessage. Instead we’ll encapsulate our calls to it within an async function.

<span class="token keyword">function</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> message <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">resolve</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token function">reject</span> <span class="token punctuation">(</span><span class="token string">'not a string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">run</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// hello</span>
    console<span class="token punctuation">.</span><span class="token function">log</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// throws an error</span>
    <span class="token punctuation">}</span>
  <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// not a string</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

<span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

All functions prefixed with async will return a promise. In this case, we don’t care because run doesn’t return anything.

We can also prefix synchronous function calls with await (within an async function) and it doesn’t care.

When you return a value from an async function it’s the same as passing that value through resolve and if you throw an error it’s the same as passing the error through reject.

Here’s one more example that shows these cases:

<span class="token keyword">function</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> message <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">resolve</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token function">reject</span> <span class="token punctuation">(</span><span class="token string">'not a string'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">synchMessage</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> message<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">run</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> message <span class="token operator">=</span> <span class="token string">'+'</span><span class="token punctuation">;</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    message <span class="token operator">+=</span> <span class="token keyword">await</span> <span class="token function">synchMessage</span> <span class="token punctuation">(</span><span class="token string">'1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    message <span class="token operator">+=</span> <span class="token keyword">await</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    message <span class="token operator">+=</span> <span class="token keyword">await</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// throws an error</span>
    message <span class="token operator">+=</span> <span class="token keyword">await</span> <span class="token function">timedMessage</span> <span class="token punctuation">(</span><span class="token string">'3'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// not a string</span>
    <span class="token punctuation">}</span>
  <span class="token keyword">return</span> message<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span> <span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// +12</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

Hopefully, this sheds some light on what’s happening under the hood with async/await and how they are related to promises and callbacks.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.