Search code examples
javascriptloopspromisees6-promise

How to use Promises in a loop and ensure all have fulfilled before continuing?


I'm using the localforage library to access localStorage/IndexedDB. To retrieve an item, the localforage.getItem() function is called, which returns a Promise that fulfills when the data is retrieved.

I need to iterate through the localforage keys, calling 'getItem' on any key that matches my criteria, and placing the value of that key in the 'matches' array. However, I don't want to continue the function until I'm sure all the values have been successfully added to 'matches'.

I am quite new to Promises, and I can't figure out how to wait until all the getItem() Promises have fulfilled before I move on.

I realize localforage has an 'iterate' function, but I'm really interested in becoming more proficient in the use of Promises, and would really like to know how this should work.

Here's what I'm doing:

var matches = [];  // Array to store matched items

localforage.keys()  // Get all keys in localforage
    .then(function(keys)  // When all keys are retrieved, iterate:
    {
        for(var i in keys)
        {
            // If the current key matches what I am looking for, add it to the 'matches' array.
            if (keys[i].indexOf('something i am looking for') > -1)
            {
                // Here I need to add this value to my array 'matches', but this requires using the getItem method which returns a Promise and doesn't necessarily fulfill immediately.
                localforage.getItem(keys[i])
                    .then(function(value)
                    {
                        matches.push(value);
                    });
              }
          }
      });

// At this point, I want to proceed only after *all* matches have been added to the 'matches' array (i.e. the getItem() Promises are fulfilled on all items in the loop).

How do I do this? Is this where the 'await' expression is applied? For example, should I do

await localforage.getItem(keys[i])
    .then(function(value)
    ... etc

Would this make the getItem function synchronous?

Thanks for any suggestions/pointers.


Solution

  • You can use Promise.all() in this situation. The basic idea is that you push a bunch of promises into and array then pass that array to Promise.all() when all the promises in the array resolve, Promise.all() resolves to the an array of the values:

    localforage.keys()  
        .then(function(keys){  
            var matches = []
            for(let i in keys) {
                if (keys[i].indexOf('something i am looking for') > -1) {
                    // matches will be an array of promises
                    matches.push(localforage.getItem(keys[i]))
                }
            }
            // Promise.all returns a promise that resolves to an array 
            // of the values they resolve to
            return Promise.all(matches)
        })
        .then(values => {
            // values is an array of your items
        })
    

    You can use async/await for this too with something like this with mocked out keys and getItems to make the snippet run:

    let localforage = {
      keys() {
        return Promise.resolve([1, 2, 3, 4, 5])
      },
      getItem(k) {
        return Promise.resolve("found: " + k)
      }
    }
    
    
    async function getStuff() {
      let matches = []
      let keys = await localforage.keys()
      for (let key of keys) {
        matches.push(await localforage.getItem(key))
      }
      return matches
    }
    
    getStuff()
      .then(values => {
        console.log(values)
      })