简体   繁体   中英

Apply async function to each element in JavaScript Promise

Can't quite understand how to do this in Promises, as I started learning about them today. What I am trying to do is that:

  • Send a GET request and iterate until final page is reached (I can do this)
  • Concatenate response elements to an array (an array of JSON objects, also done)
  • For each element in this array, perform async operation such as image upload and database query (stuck here)

Here's what I have done so far:

Following function iterates over all pages.

function getAllCountries(requestURL, pageNr, countries) {
  return Request({
    'method': 'GET',
    'uri': requestURL,
    'json': true,
  }).then((response) => {
    if (!countries) {
      countries = [];
    }

    countries = countries.concat(response.data);
    const meta = response.meta;

    if (pageNr < meta['pagination']['total_pages']) {
      pageNr += 1;
      let formattedLink = Format(BASE_URL, API_TOKEN, pageNr);
      return getAllCountries(formattedLink, pageNr, countries);
    }

    return countries;
  });
}

Here is where I am having trouble:

getAllCountries(formattedLink, pageNr)
  .then((countries) => {
    // will call handleCountry method for each country here

    // how do I handle the callback to/from handleCountry here?
    // that is wrong
    // countries.map(handleCountry);
  })
  .then(() => {
    console.log('Handled all countries');
    return res.sendStatus(200);
  })
  .catch((error) => {
    console.log(error);
    return res.sendStatus(500);
  });

Here is how handleCountry function is:

function handleCountry(country, callback) {
  // do stuff here
  if (!country["extra"]) {
    app.models.Country.upsert(countryJson, callback);
  } else {
    // do async stuff here, image upload etc with Async.auto
    Async.auto({
      'uploadImage': (autoCallback) => {
        uploadImage(autoCallback);
      }
      'updateOnDb': ['uploadImage', (results, autoCallback) => {
        // do stuff
        app.models.Country.upsert(countryJson, autoCallback);
      }
    }, callback);
  }
}

What should I do here? The order of handling countries is not important, by the way.

function getAllCountries(requestURL, pageNr, countries) {
    Request({
        'method': 'GET',
        'uri': requestURL,
        'json': true,
    }).then((response) => {
        if (!countries) {
        countries = [];
        }

        countries = countries.concat(response.data);
        const meta = response.meta;

        if (pageNr < meta['pagination']['total_pages']) {
            pageNr += 1;
            let formattedLink = Format(BASE_URL, API_TOKEN, pageNr);

            // declaring array to be passed inside promise.all
            let countriesPromises = [];


            // creating promises for each country
            countries.forEach(function(country) {
                countriesPromises.push(new Promise(function(){
                    handleCountry(country,callback);
                })
            });

        }

        // callback for all countries.
        Promise.all(countriesPromises)
            .then(() => {
                console.log('Handled all countries');
                return res.sendStatus(200);
            })
            .catch((error) => {
                console.log(error);
                return res.sendStatus(500);
            })
    }   

}

After getting all the countries we create promise for each strong them in an array and finally passing them into promise.all() which allows us to bind callbacks for completion for all promises. Note that i have removed all return statement and getAllCountries is no longer thenable, we have implemented the callbacks inside of getAllCountries itself. You may segregate it as per your need ahead.

Problem is that handleCountryAsync() isn't playing the "promises game" - it accepts a callback instead of returning a promise.

You could promisify by rewriting handleCountry() but it's just as simple to leave handleCountry() intact and write an adaptor, handleCountryAsync() as follows :

function handleCountryAsync(country) {
    return new Promise((resolve, reject) => {
        try {
            handleCountry(country, (...args) => {
                resolve(args); // promises can be fulfilled only with a single value, but multiple arguments can be delivered as an array.
            });
        }
        catch(error) {
            reject(error);
        }
    });
}

Now, the need for an overt callback in the higher level code disappears and countries.map() can return an array of promises to Promise.all() :

getAllCountries(formattedLink, pageNr)
.then(countries => Promise.all(countries.map(handleCountryAsync)))
.then(() => {
    console.log('Handled all countries');
    return res.sendStatus(200);
})
.catch((error) => {
    console.log(error);
    return res.sendStatus(500);
});

That's the essence of it ayway. Other considerations revolve chiefly around error handling. For example, you have a design decision - whether to swallow errors or allow a single failure of handleCountryAsync to give rise to a 500. As writen above, it is the latter.

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