0

I have the following function:

function ipfsRetrieve( ipfsHash ){
  return new Promise( function( resolve, reject ) {
    ipfs.catJSON( ipfsHash, (err, result) => {
        if (err){                
            reject(err);
        }
        resolve( result);           
    });
});    

}

Now, when I call this function inside a loop as below:

var hashArray   = [ "QmTgsbm...nqswTvS7Db",
                "QmR6Eum...uZuUckegjt",
                "QmdG1F8...znnuNJDAsd6",                    
              ]

var dataArray   = []; 
hashArry.forEach(function(hash){
    ipfsRetrieve( hash ).then(function(data){
        dataArray.push(data);
    });
});  

return dataArray

The "return dataArray' line returns an empty array. How should I change this code to have the "dataArray" filled with the data retrived from IPFS?

we.are
  • 409
  • 2
  • 6
  • 15
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – ASDFGerte Aug 07 '18 at 16:33
  • The question is about dealing with multiple async requests in a loop. – Steven Spungin Aug 07 '18 at 16:38
  • 1
    Possible duplicate of [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Get Off My Lawn Aug 07 '18 at 16:43

2 Answers2

4

You should use Promise.all.

Construct an Array of Promises and then use the method to wait for all promises to fulfill, after that you can use the array in the correct order:

let hashArray = ["QmTgsbm...nqswTvS7Db",
  "QmR6Eum...uZuUckegjt",
  "QmdG1F8...znnuNJDAsd6",
]

// construct Array of promises
let hashes = hashArray.map(hash => ipfsRetrieve(hash));

Promise.all(hashes).then(dataArray => {
  // work with the data
  console.log(dataArray) 
});
Luca Kiebel
  • 9,790
  • 7
  • 29
  • 44
1

For starters, you need to return after rejecting, or else your resolve will get called too.

function ipfsRetrieve( ipfsHash ){
  return new Promise( function( resolve, reject ) {
    ipfs.catJSON( ipfsHash, (err, result) => {
        if (err){                
           reject(err);
           return;
        }
        resolve( result);           
    });
});   

Now for the loop, use map instead of forEach, and return the promise. Then wait on the promises.

let promises = hashArry.map(hash=> return new Promise(resolve,reject) { // your code here handling hash, updating theData, and then resolving })
return Promise.all(promises).then( ()=> return theData)

In your case, the promise is provided by ipfsRetrieve, so you would call

let promises = hashArry.map(ipfsRetrieve)
return Promise.all(promises)

The caller of your functions will do this:

ipfsRetrieve().then(data=>{ // process data here } )

If you are cool with async await, do this. (marking containing function as async)

let data = await ipfsRetrieve()
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
  • 1
    "*code here updating theData and then resolving*" - no no no. Call `ipfsRetrieve` and use `then`, don't use `new Promise` – Bergi Aug 07 '18 at 16:37
  • I think it's pretty clear what the OP wanted to achieve with `ipfsRetrieve( hash ).then(function(data){ dataArray.push(data); })`. When using `Promise.all` properly, you don't need any "data updating" at all. – Bergi Aug 07 '18 at 16:48
  • Someone is going to want the data when the promises all resolve. – Steven Spungin Aug 07 '18 at 16:50
  • Yes, and that's what `Promise.all` does for you – Bergi Aug 07 '18 at 16:51
  • The original function returns a data array. You can't do that, you have to return a promise and resolve it. This is getting silly. – Steven Spungin Aug 07 '18 at 16:55
  • `return Promise.all(hashArray.map(ipfsRetrieve));` is all that is necessary. No need to introduce and fill some explicit `dataArray`. – Bergi Aug 07 '18 at 17:56
  • 1
    Yes that's true, Promise.all returns an array of the resolved values. – Steven Spungin Aug 07 '18 at 19:13