Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return values from async functions using async await from a callback function?

I'm new to nodejs and it's callback hell, I read about async / await introduction in node 8 and was interested to implement it that way

I have a specific set of methods that I need to call in a synchronous manner one after another for trello API e.g

  1. create board
  2. create labels using board id
  3. create cards using board id
  4. attach labels to card
  5. create list in card
  6. add each item to list in a card

you can imagine in nodejs, this requires significant callbacks nested into one another to access the previous object

createProjectBoard: function (project) {
        t.post("1/board", {
            name: project.name,
            desc: project.description,
            defaultLists: false
        }, function (err, board) {
            if (err) {
                console.log(err);
                throw err;
            }

            //get board id from data
            let boardId = board.id
            let backlogListId = "";
            let highId = "", mediumId = "", lowId = "";

            //create labels
            t.post("1/labels", {
                name: 'High',
                color: 'red',
                idBoard: boardId
            }, function (err, label) {
                console.log(err || 'High label created');
                if (err) return;
                highId = label.id;
            });

            t.post("1/labels", {
                name: 'Medium',
                color: 'orange',
                idBoard: boardId
            }, function (err, label) {
                console.log(err || 'Medium label created');
                if (err) return;
                mediumId = label.id;
            });

            t.post("1/labels", {
                name: 'Low',
                color: 'yellow',
                idBoard: boardId
            }, function (err, label) {
                console.log(err || 'Low label created');
                if (err) return;
                lowId = label.id;
            });

            //create rest of the lists
            t.post("1/lists", { name: "Completed", idBoard: boardId }, function (e, l) {
                if (e) {
                    console.log(e);
                    return;
                }
                console.log(l);
                t.post("1/lists", { name: "Testing", idBoard: boardId }, function (e, l) {
                    if (e) {
                        console.log(e);
                        return;
                    }
                    console.log(l);
                    t.post("1/lists", { name: "In Progress", idBoard: boardId }, function (e, l) {
                        if (e) {
                            console.log(e);
                            return;
                        }
                        console.log(l);

                        //create backlog list
                        t.post("1/lists", { name: "Backlog", idBoard: boardId }, function (e, list) {
                            if (e) {
                                console.log(e);
                                return;
                            }
                            console.log(list);
                            backlogListId = list.id;
                            console.log("backlog card list id:" + backlogListId);

                            _.each(project.userStories, function (story) {
                                //assign labels
                                let labelId = "";
                                switch (story.complexity.toLowerCase()) {
                                    case 'high':
                                        labelId = highId;
                                        break;
                                    case 'medium':
                                        labelId = mediumId;
                                        break;
                                    default:
                                        labelId = lowId;
                                }

                                t.post("1/cards", {
                                    name: story.title,
                                    idLabels: labelId,
                                    idList: backlogListId
                                }, function (e, card) {
                                    if (e) {
                                        console.log(e);
                                        return;
                                    }
                                    let cardId = card.id;
                                    console.log("created id:" + cardId + ";card:" + story.title);                                    

                                    t.post("1/cards/" + cardId + "/checklists", {
                                        name: "Acceptance Criteria"
                                    }, function (e, checklist) {
                                        if (e) {
                                            console.log(e);
                                            return;
                                        }
                                        console.log('checklist created:');
                                        var clId = checklist.id;
                                        _.each(story.criterion, function (criteria) {
                                            t.post("1/cards/" + cardId + "/checklist/" + clId + "/checkItem", {
                                                name: criteria
                                            }, function (e, checkItem) {
                                                if (e) {
                                                    console.log(e);
                                                    return;
                                                }
                                                console.log('created check item:' + checkItem);
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    }

I'm still having issues with the above code where __.each loop is involved, it calls all the functions in the loop asynchronously (re-arranging the order of items in which they were supposed to be originally) - so I thought that there has to be a better way to make the calls synchronously

I'm interested in using await / async to clean out the code , but running into some trouble on returning the object from the async callback

the solution is based in sails.js, the following is an excerpt from the TrelloService I'm writing

consider the following:

 createProjectBoard: async function(project) {
        //get board id from data
        let board;
        let boardId = "";
        let backlogListId = "";
        let highId = "",
            mediumId = "",
            lowId = "";


        try {
            await t.post("1/board", {
                    name: project.name,
                    desc: project.description,
                    defaultLists: false
                },
                function(err, b) {
                    if (err) {
                        console.log(err);
                        throw err;
                    }
                    console.log("board" + b);
                    board = b;
                });

            //create labels
            await t.post("1/labels", {
                name: 'High',
                color: 'red',
                idBoard: board.id
            }, function(err, label) {
                console.log(err || 'High label created');
                if (err) return;
                highId = label.id;
            });

        } catch (err) {
            console.log(err);
        }
}

I need the board value to be available in the labels request call, so far I'm unable to retrieve the board object, event though I've setup await keywords

I need to be able to get objects from call back functions and use them into subsequent function calls in a synchronous manner

I'm using a trello api wrapper node-trello to make the calls (t)

One way would be wrap the above in more functions with a callback as follows, but I don't think that'd be best practice, as I'd have to write wrapper callbacks on every object that I need to use

function foo(url,options,cb){
await t.post(url, options,
        function(err, b) {
            if (err) {
                console.log(err);
                throw err;
            }
            console.log("board" + b);
            cb(b);
        });
}

var url = "1/board";
var options = {
            name: project.name,
            desc: project.description,
            defaultLists: false
        };

foo(url,options,function(board){
   console.log(board); //board object
});

any suggestions are appreciated

like image 469
Danish Avatar asked Jul 27 '17 13:07

Danish


People also ask

Can we return value from async function?

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise. Note: Even though the return value of an async function behaves as if it's wrapped in a Promise.resolve , they are not equivalent.

Can I use await in a callback function?

The await keyword is used in an async function to ensure that all promises returned in the async function are synchronized, ie. they wait for each other. Await eliminates the use of callbacks in .

Can you await a function that returns a promise?

The keyword await is used to wait for a Promise. It can only be used inside an async function. This keyword makes JavaScript wait until that promise settles and returns its result.

Does await return a promise or a value?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.


1 Answers

I'm interested in using await / async to clean out the code

There are two steps to this: promisification and async. Your code is getting confused because it's skipping the first step.

Promisification creates very simple promise-returning wrapper functions around the callback functions. For example, if t is an instance of a Trello class:

Trello.prototype.postAsync = (url, data) => new Promise((resolve, reject) => {
  this.post(url, data, (err, result) => {
    if (err) { reject(err); }
    else { resolve(result); }
  });
});

The second step is to write your async/await logic, using promise-returning functions and not callbacks. Since they are promise-returning, they code is much more natural:

const board = await t.postAsync("1/board", {
    name: project.name,
    desc: project.description,
    defaultLists: false
});
console.log("board" + board);

//create labels
let highId;
try {
  highId = await t.postAsync("1/labels", {
      name: 'High',
      color: 'red',
      idBoard: board.id
  });
} catch (err) {
  console.log(err || 'High label created');
  return;
}

The promisification step is tedious and repetitive. There are some libraries that can automate callbacks-to-promises, most notably Bluebird.

like image 109
Stephen Cleary Avatar answered Oct 20 '22 21:10

Stephen Cleary