How to rewrite a callback function in Promise form and async/await form in JavaScript

Corey Cleary on January 16, 2019

Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up t... [Read Full]
markdown guide
 

I remember this picture

https://cdn-images-1.medium.com/max/823/1*Co0gr64Uo5kSg89ukFD2dw.jpeg

I don't know if there a promise hell example. Thanks for this great article.

 

Haha yeah this one's classic - good point too, you can hit "promise hell" too if you're not careful. Not as "pyramid" like as callback hell, but having ton's and tons of then's can get sort of annoying

 

Promise hell can't exist, that's why it's so nice!

const call_api = (url) => fetch(url).then(res => res.json())
const heaven = (url) => call_api(url).then(data => console.log(data))

heaven('/api/users')
  .then(() => heaven('/api/posts'))
  .then(([{ id }]) => heaven(`/api/posts/reactions?post=${id}`))
  .then(({ post: id }) => heaven(`/api/comments?post=${id}`))

See? It's really clean 😃

 

Think also if it's also with If-Else Statements :)

 

In addition to setTimeout(), other asynchronous operations you're likely to see in the real-world are: AJAX and HTTP calls, database calls, filesystem calls (in the case of Node, if no synchronous version exists), etc.

I can appreciate trying to dispense some knowledge, but why not use one of those "real-world" cases in the example instead of the highly abstract setTimeout()?

I've been trying to grasp promises and async/await for "a while" now, but examples and articles I find like this, as you've said, use the wholly unrealistic setTimeout().

I completed a personal project a few months ago in NodeJS that was pretty standard AJAX activity . . .

  1. Get data from front end.
  2. Build it to JSON.
  3. Check if a file exists.
  4. If not, open a new file.
  5. If ok, write the file.
  6. If ok, close the file.
  7. If ok, tell the front end to show the new entry.

It took finishing it to realize that I built a several indention deep callback hell. Great. Well at least now I have a base to find examples for promises.

Not. Every example on the first page of my various google searches all used setTimeout().

I'd love to see an example of callback > promise > async/await based on, as you've said, realistic file, database or CORS calls.

 

Real example and also I would simplify the error handling a ton and lose the try catch.

/**
 * Async Await Error Handling
 *
 * Example:
 *  const [err, response] = await to(
 *    axios.get(url)
 *  );
 * if (err) throw new Error('u broke it');
 * const hi = response.hi;
 *
 * @module to
 */

export default (promise) =>
  promise.then((data) => [null, data]).catch((err) => [err]);

You can pass anything that returns a promise to that module like so.

So here we are using the WP backbone api to return a collection of whatever and checking if more exist each time, when its all done we are combining them all together and loading the result into the data variable so we can stop our loading event on the table and show all the results. The js api will only return 100 items in a collection at a time and in this case we needed to grab 200-300 of whatever and load the table with all the data so the searching and sorting is instant so we sacrifice the inital time to grab all the data from better UI once we do.

PS: This is the basic layout of a Vue component, I just left off the methods wrapper around the loadItems function so you could see it easier.

import to from './to';

export default {

  data() {
    return {
      tableLoading: false,
      allTasks: []
    };
  },

  async loadItems() {

    // Start loading.
    this.tableLoading = true;

    // All our things will be put here.
    let all = [];

    // Get the collection of tasks.
    const tasksCollection = new wp.api.collections.Task();

    // Now we can use our to module for error handling.
    // collection.fetch could be axios or whatever returning a promise.
    const [err, response] = await to(
      tasksCollection.fetch({
        data: { per_page: 100 }
      })
    );
    if (err) throw new Error('Failed to load task collection');

    // We have lodashES in our app already so union() is available here.
    all = union(all, response);

    // This is checking if more exist, but it could be anything
    // your flow was dependant on.
    const hasMore = () => tasksCollection.hasMore();

    // Looping through another chunk.
    const loopMore = async () => {
      if (hasMore()) {
        const [errMore, responseMore] = await to(tasksCollection.more());
        if (errMore) throw new Error('Failed to load task collection');
        all = union(all, responseMore);
        loopMore();
      } else {
        // No more we are done, up yonder in the file we are
        // waiting for allTasks to be set to do other things.
        this.allTasks = all;
      }
    };

    loopMore();
  },

}

With async await and your example you described you would basically have a method or function returning a promise for checking on if the file exists and reading the data from it, probably using axios or fetch. Then another method returning a promise to create and write to a file. Each method would return a response and you would await on the result before doing the next part.

Hope this gave you a real world example to see how this stuff can be used.

 

Yeah I've got another post in my backlog that will be something more along the lines of, finding some open source code snippet written using callbacks and refactoring the whole thing to show the before and after.

This post is really meant more to show the process, and I didn't want to overwhelm with too much information. However I understand some people learn better if they see something taken from an existing application and rewritten.

Thanks for the comment

 

Use bluebirds .from callback function to easily to await on a function that takes a callback).

This only works if your callbacks are called in the standard way though, ie (err, result).

const Promise = require('bluebird');

await Promise.fromCallback(callback => functionThatTakesACallback(arg1, arg2, callback));

 
code of conduct - report abuse