2

I am struggling with promises. I see how the chain of events is happening through .then().then().then().then().then().then().then().then().then().then().then().then() but I can not figure out how to make it end. I was hoping I could just make a simple :

 .then(callback(mydata))

However, I can't get that to work. I am trying to accomplish this.

function doSomethingCallback(theArrayComesBackHere) {
    theArrayComesBackHere.forEach(/*do stuff*/);
}    

button.onclick = () => {
   myobj.getlocalforagedata(doSomethingCallback);
}

myobj = {
   getlocalforagedata: (callback) => {
      var arr = [];
      localForage.keys().then((keys) => {
          keys.forEach((key) => {
              localForage.getItem(key).then(function (results) {
                  arr.push(results);
              });
          });
          callback && callback(arr);
      });
   }
}

Please help me break out of this madness.

pirs
  • 2,410
  • 2
  • 18
  • 25
perl2go
  • 23
  • 4
  • 1
    It's unclear, you want a promise in your function, right ? – pirs Nov 10 '17 at 17:34
  • what i want is to have this promise return a single object. (honestly, i wish promises weren't involved. i never use them because i can't seem to grasp them. but, i am using localForage and it uses them. i would like to get localForage do it's thing and i build a single object to be returned) --- hope that helps – perl2go Nov 10 '17 at 17:36
  • a single object for all function? – pirs Nov 10 '17 at 17:40
  • yes, just a single object to come out of localForage's promise. – perl2go Nov 10 '17 at 17:42
  • `localForage.getItem(key).then..` - `LocalStorage.get` doesn't return a `Promise`. – nicholaswmin Nov 10 '17 at 17:42
  • in there is where i am building my array... how do i return that array so that the doSomethingCallback() can work with it? – perl2go Nov 10 '17 at 17:44
  • what return localforage exactly ? its easy dw. – pirs Nov 10 '17 at 17:45
  • that could be the part i don't grasp with promises. i don't understand how a promise returns anything. i have been able to chain then()'s and make things work. but, i don't know how to return data from a promise. – perl2go Nov 10 '17 at 17:48

3 Answers3

4

The [previously] accepted answer to this question is using the explicit promise creation antipattern, a practice that makes code extra complicated and buggy.

You can accomplish what you are trying to do far more cleanly, like this:

function getLocalForageData() {
    return localForage.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            return localForage.getItem(key);
        });
    });
}

Example usage:

getLocalForageData()
    .then(function (values) {
        console.log(values);
    })
    .catch(function (error) {
        console.error(error);
    });

The above obtains an array of values, which are not matched up with their respective keys. If you would like the values paired with their keys, you can do this:

function getLocalForageData() {
    return localForage.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            return localForage.getItem(key)
                .then(function (value) {
                    return { key: key, value: value };
                });
        }));
    });
}

Or you could break out one of the inner functions to reduce the nesting:

function getLocalForageValueWithKey(key) {
    return localForage.getItem(key)
        .then(function (value) {
            return { key: key, value: value };
        });
}

function getLocalForageData() {
    return localForage.keys().then(function (keys) {
        return Promise.all(keys.map(getLocalForageValueWithKey));
    });
}

In either case, the code to call and use the getLocalForageData function would be the same as above.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • whoa! thanks much. i'll try to work my way through this one. i did the examples as suggested and other than a missing ), they work perfectly. wow, wow, wow. i have much to learn on promises. – perl2go Nov 10 '17 at 19:36
  • @perl2go I think I've fixed the missing parentheses now. Thanks for pointing that out. Promises are great once you get the hang of them! – JLRishe Nov 10 '17 at 19:40
1

[Edit] The JLRishe's answer is not bad but it returns an array of objects hardly readable for next uses (you'll need a loop to get the values), and also seems to forget null values, so the complete and trickery solution should be more like:

  const getLocalForageDataByKeys = () => {
    return localForage.keys().then(keys => {
      return Promise.all(keys.map(key => {
        return localForage.getItem(key)
          .then(value => {
            return { [key]: value }
          })
          .catch(error => {
            console.log(error)
            return { [key]: null }
          })
      })).then(arr => {
        return Object.assign(...arr)
      })
    })
  }

Usage:

getLocalForageDataByKeys()
  .then(obj => {
    console.log(obj) // { keyA: 'valueA', keyB: 'valueB' }
  }).catch(error => {
    console.log(error) // do sth with error
  })

Note:

Better yet, this solution use ES6 and jsLint highest quality standards

More:

Promise examples with localForage : https://localforage.github.io/localForage/#data-api-getitem

Using promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Using Object assign: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Using spread operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

pirs
  • 2,410
  • 2
  • 18
  • 25
  • 1
    THAT DID IT!!! THANK YOU SO MUCH. I will study this one intensely. thank you so so very much. – perl2go Nov 10 '17 at 17:54
  • You are welcome, promises are weird to understand first, but finally very useful – pirs Nov 10 '17 at 18:02
  • But `getlocalforagedata` is supposed to be a function, not a promise. – Roamer-1888 Nov 10 '17 at 18:11
  • what was posted "connected the dots" for me and i was able to get the rest. getting that array out of the promise was my primary issue. – perl2go Nov 10 '17 at 18:19
  • i will get a simple example ready and post my result. hope it helps someone else. – perl2go Nov 10 '17 at 18:20
  • Even ignoring the fact that `getlocalforagedata` should be a function not a promise, this answer can't possibly work. The tests `if (err.length > 0)` and `if (arr.length > 0)` will be performed with no cognisance of the results delivered by `localForage.keys().then(...)`; `err.length` is guaranteed to be `0` and `reject([])` will be called synchronously. – Roamer-1888 Nov 10 '17 at 18:36
  • @Roamer-1888 pirs has since edited the answer to produce a promise that never resolves or rejects. – JLRishe Nov 10 '17 at 19:19
  • ahhhhhhhhhh, yup. i got it working by only taking the values that i wanted into consideration. (ignored the errors part because i'm not interested in the errors at the moment). but, once i put the error stuff in, the err[] did not work. – perl2go Nov 10 '17 at 19:24
  • @perl2go That's a better solution yet :-) – pirs Nov 12 '17 at 19:13
0

Several features of the code in the question are not necessary :

  • new Promise(...): localForage.keys() returns a promise which forms the root of a promise chain.
  • callback: by returning a promise from getlocalforagedata(), the caller can chain getlocalforagedata().then(...) avoiding the need for a callback to be passed.
  • Outer var arr: a correctly constructed promise chain can gather and deliver results without any outer variables.

The following are necessary :

  • to aggregate the promises returned by localForage.getItem(key) and to return a promise from that step in the chain.
  • to perform the tests if (err.length > 0) and if (arr.length > 0) in a further chained .then() that waits for the localForage.getItem(key) promises to settle.

Try this :

myobj = {
    getlocalforagedata: () => {
        return localForage.keys()
        .then(keys => {
            // Use `keys.map(...)` to return an array of promises.
            // Then use `Promsie.all()` to aggregate the promises returned by `localForage.getItem(key)`.
            return Promise.all(keys.map(key => localForage.getItem(key)));
        })
        .then(arr => { // an array of results
            // These tests must be performed in a chained `then()`,
            // which will wait for all the individual `localForage.getItem(key)` results (or errors) to be delivered
            if (arr.length > 0) {
                return arr; // deliver a non-empty array
            } else {
                throw new Error('no results'); // throw error down the promise chain's error path.
            }
        });
    }
}

Call as follows :

myobj.getlocalforagedata()
.then((results) => {
    // Work with results
})
.catch((error) => {
    // Do whatever with error.
    // Either return some default value or re-throw `error`
});

getlocalforagedata() could be made to work with a callback but it's much better practice to return a promise and allow the caller to chain getlocalforagedata().then(...).

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44