Simon
Simon

Reputation: 310

ES6 Dyanmic Promise Chaining from array

Scenario

I have an array of URLs that I need to download, however each must also be supplied with a unique transaction ID that must be requested from the server and only increments when a request is successful.

Problem

As I loop through the array I need to wait for both the request for the transaction ID and the request for the file to complete before starting the next iteration of the loop but the number of files is not fixed so need to dynamically build a chain of promises.

Pseudocode

Below is some pseudocode, getFiles() is the problem because all the requests get the same transaction Id as they don't wait for the previous request to finish.

function getTransationId(){
    return new Promise((resolve,reject)=> {
        let id = getNextTransactionId();
        if(id!=error){
            resolve(id);
        }else{
            reject(error);
        }
    })
}

function getFile(url, transactionId){
    return new Promise((resolve,reject)=>{
        http.request(url+transactionId, function(err,response){
            if(err){
                reject(err);
            }else{
                resolve(response);
            }
        });
    });
}

function getFilesFromArray(urlArray){
    for(let url of urlArray){
        getTransactionId().then(resolve=>getFile(url,resolve),reject=>console.error(reject));
    }
}

Question

How do I chain chain promises together dynamically?

Answer

Here's a JSFiddle of Ovidiu's answer

Upvotes: 1

Views: 976

Answers (4)

amaksr
amaksr

Reputation: 7745

You can simplify logic if you run it via synchronous executor nsynjs. Nsynjs will pause when some function evaluates to promise, and then assigns the result to data property. The code will transform like this:

function getFilesFromArray(urlArray){
    for(var i = 0; i<urlArray.length; i++) {
        var trId = getTransactionId().data;
        // trId is ready here
        var fileContent = getFile(urlArray[i],trId).data;
        // file data is ready here
        console.log('fileContent=',fileContent);
    };
};

nsynjs.run(getFilesFromArray,{},urls,function(){
    console.log('getFilesFromArray is done');
});

getFilesFromArray can be further simplified to:

function getFilesFromArray(urlArray){
    for(var i = 0; i<urlArray.length; i++) {
        var fileContent = getFile(urlArray[i],getTransactionId().data).data;
        console.log('fileContent=',fileContent);
    };
};

Upvotes: -1

Ovidiu Dolha
Ovidiu Dolha

Reputation: 5413

A functional approach is to use reduce to iterate and return a final promise chained up from each sub-promise. It also helps building the results e.g. in an array:

function getFilesFromArray(urlArray){
    const filesPromise = urlArray.reduce((curPromise, url) => {
        return curPromise
           .then(curFiles => {
                return getTransactionId()
                    .then(id => getFile(url, id))
                    .then(newFile => [...curFiles, newFile]);
           });
    }, Promise.resolve([]));

    filesPromise.then(files => {
         console.log(files);
    }
}

This effectively builds a promise chain that:

  • starts with a static Promise with a value [] representing the initial set of files: Promise.resolve([])
  • on each iteration, returns a promise that waits for the curPromise in the chain and then
  • performs getTransactionId and uses the id to getFile
  • once the file will be retrieved it will return an array containing the curFiles set in the the curPromise (previous values) and concatenates the newFile into it
  • the end result will be a single promise with all files collected

Upvotes: 7

imNotWhoYouSayIam
imNotWhoYouSayIam

Reputation: 100

Try using async/await.

Read more here

async function getFilesFromArray(urlArray) {
      for(let url of urlArray){
         //wrap this in a try/catch block if you want to continue with execution 
         //if you receive an error from one of the functions
         const transactionId =await getTransactionId()
         const file = await getFile(url,transactionId)
}
}

Upvotes: 0

Frederik Hansen
Frederik Hansen

Reputation: 506

You can do something along these lines

function getAllFiles(i, results, urlArray) {
    if(i == urlArray.length) return;

    getTransationId().then(id => {
        return new Promise((resolve, reject) => {
            http.request(urlArray[i] + id, (err, response) => {
                if(err){
                    reject();
                }else{
                    results.push(response);
                    resolve();
                }
            });
        });
    }).then(() => {
        getAllFiles(i + 1, results, urlArray);
    })
}

Upvotes: 0

Related Questions