简体   繁体   中英

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

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 eg 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

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);
    })
}

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)
}
}

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);
    };
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM