ashishsanjayrao
ashishsanjayrao

Reputation: 101

Save value of then in a variable outside of the Promise

I'm fairly new to the concept of Promises and I'm trying to understand how the scopes work. I'm basically trying to store the value from inside a then() into a variable outside of the Promise

Below is a simple function I've written in Nodejs (Express), using Sequelize to run the query on the DB.

exports.getTest = (req, res, next) => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    for (var i = 0; i < names.length; i++) {
        model.Category.findOne({
            where: {
                name: names[i]
            },
            attributes: ['id']
        }).then(id => {
            categories.push(
            {
                category_id: id.id
            });
        });
    }
    res.json(categories);
}

I have other logic to be run after that, and I have a for loop around the Promise. So, I can't run my next logic inside the then, otherwise I'll have it running multiple times because of the for loop. I need to populate the array categories to use it in my next operation.

Currently, my response (res.json(categories)) is []

Any help would be appreciated.

PS: I know this is a common topic, but as I mentioned, I'm fairly new to this and the other answers didn't fit my scenario and were further confusing me.

Thanks in advance!

Upvotes: 2

Views: 9702

Answers (3)

jimmy5312
jimmy5312

Reputation: 429

You can try Promise.all()

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

The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.

var getTest = (req, res, next) => {
    var categories = [];
    var promises = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    var resolveCount = 0;
    for (var i = 0; i < names.length; i++) {
        // Store the name in variable so that can be persistent and not
        // affected by the changing 'i' value
        const name = names[i]
        promises.push(new Promise((resolve, reject) => {
        
          // Your DB calls here. We shall use a simple timer to mimic the
          // effect
          setTimeout(() => {
            categories.push(name)
            resolveCount++;
            resolve();
          }, 1000)
        }));
    }
    
    
    Promise.all(promises).then(function() {
      console.log("This should run ONCE before AFTER promise resolved")
      console.log("resolveCount: " + resolveCount)
      console.log(categories);
      
      // Do your logic with the updated array
      // res.json(categories);
    });
    
    console.log("This will run immediately, before any promise resolve")
    console.log("resolveCount: " + resolveCount)
    console.log(categories)
}

getTest();

Upvotes: 1

Liondj
Liondj

Reputation: 48

exports.getTest = (req, res, next) => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    names.forEach(name => {
        Category.findOne({where: {name: name}}).then(category => {
            categories.push({category_id: category.id})
        })
    })
    res.json(categories);
}

So basically, model.findeOne() returns a promise with the object of the first category with each of the names. then() catches that promise, resolves it and you give it a callback function that get that object passed as a parameter.

It could look something like

Categories.findOne({where: {name: name}).then(function(category){
    // do something with that category
})

But the arrow function makes it a lot more readable as then(category => {//some code}).

Upvotes: -1

Isaac Vidrine
Isaac Vidrine

Reputation: 1666

In your case, categories will always return [] because you aren't waiting for all your promises to finish before returning your response. For loops do not wait for asynchronous actions to complete before continuing on to the next iteration. Therefore the loop ends, and a response is returned before any of them finish.

Instead of calling promises within a for loop, you should push them to an array, which you can then pass to Promise.all() function.

Here is what it should look like

exports.getTest = () => {
    var categories = [];
    var names = ['Category 1', 'Category 2', 'Category 3', 'Category 4'];
    var promiseArray = [];
    for (var i = 0; i < names.length; i++) {
        promiseArray.push(
          model.Category.findOne({
              where: {
                  name: names[i]
              },
              attributes: ['id']
          }).then(id => {
              categories.push(
              {
                  category_id: id.id
              });
          });
        )
    }

    return Promise.all(promiseArr)
}

getTest() now returns a promise, so it can be called like this

getTest()
  .then(data => {
    // data will be an array of promise responses
  }).catch(err => {
    console.log(err);
  })

Upvotes: 3

Related Questions