How can I turn a javascript callback thread into a Promise?

function getMentionedUsers(str, next){
    var array = getUsernamesFromString(str); //['john','alex','jess'];
    if(array.length > 0){
        var users = [];
        var pending = array.length;
        array.forEach(function(username){
            getUserByUsername(username).then(function(model){
                users.push(model.key);
                --pending || next(users); //this is a callback model
            }); 
        });
    }
};

function getUserByUsername(username){
    return admin.database().ref('/users').orderByChild('username').equalTo(username).once('value').then(function(snapshot) {
        return snapshot.val(); //this is the firebase example of a promise
    });
};

      

I am doing this now:

    getMentionedUsers(model.body, function(users){
        console.log("Mentions", users);
    });

      

However, I would like to turn getMentionedUsers

into a promise. How can i do this? I am new to Promises

+3


source to share


4 answers


You can use Promise.all

and Array#map

:

function getMentionedUsers(str) {
  return Promise.all(getUsernamesFromString(str).map((username) => {
    return getUserByUsername(username).then((model) => model.key);
  }));
}

      



The more readable version is split into two functions:

function getUserKeyByUsername(username) {
  return getUserByUsername(username).then((user) => user.key);
}

function getMentionedUsers(str) {
  const promises = getUsernamesFromString(str).map(getUserKeyByUsername);
  return Promise.all(promises);
}

      

+5


source


Use Promise.all

.

const getMentionedUsers = str =>
        Promise.all(
            getUsernamesFromString(str).map(
                username => getUserByUsername(username)
                    .then(model => model.key)
            )
        );

      



https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

+1


source


You can get the best out of both. If you pass a function next

, it will be called with the results. If not, your method will return a promise.

function getMentionedUsers(str, next){
    var array = getUsernamesFromString(str); //['john','alex','jess'];
    var promise = Promise.resolve([]); // default 
    var hasNext = typeof next === 'function';
    if(array.length > 0){
        promise = Promise.all(array.map(function(username){
            return getUserByUsername(username); 
        }));
    }
    promise = promise.then(models => {
        var users = models.map(model => model.key);
        if (hasNext) next(null, users);
        return users;
    });
    if (hasNext) promise.catch(next);
    else return promise;
};

      

UPDATE: While not part of your original question, it is still a good point and worth noting. The existing code uses a non-standard callback technology. The standard callback method assumes an error as the first parameter and the result as the second parameter:

next(new Error(...)); //-> when something fails
next(null, results); //-> when something succeeds

      

So I updated my code to show the "standard" callback behavior along with promises. Using the above hybrid approach allows existing code to be leveraged by allowing new code to use the new Promise method. This will be considered an "unchanged change".

0


source


using native ES6 promises written in functional style:

// Returns array of usernames
function getUsernamesFromString(str = '') {
  return str.split(',').map(s => s.trim())
}

// returns promise of user
function getUserByUserName(username) {
  // Lets say this is a slow async function and returns a promise
  return Promise.resolve({
    id: (Math.random() * 10 ** 10).toFixed(0),
    username
  });
}

function getMentionedUsers(str) {
  return Promise.all(
    getUsernamesFromString(str).map(getUserByUserName)
  );
}

getMentionedUsers('kai, ava, mia, nova').then(console.log.bind(console))
      

Run codeHide result


However, there are also libraries, such as bluebird , that can automatically both encode objects and functions if you follow the NODE convention (err, result)

as callback arguments.

You can also just return new Promise((resolve, reject) => { /* all your code */ })

and just call resolve(dataToResolveWith)

if it succeeds, and reject(new Error())

if it doesn't work, but you rarely have to do that and is actually an anti-pattern.

-1


source







All Articles