简体   繁体   中英

Javascript Promises: Iterate over all object keys arrays and then resolve

I have this JS object:

let setOfWords = {
    "nouns": [
        "work",
        "construction",
        "industry"
    ],
    "verbs": [
        "work"
    ],
}

I'm using the google translate API which calls a REST resource, so I need to wait for the response of each translation and then resolve the same object structure but with the translated words.

function translateByCategory(){
    let translatedObj = {};
    return new Promise(function(resolve, reject){

        Object.keys(obj).forEach(function(category){
            if (translatedObj[category] == undefined) {
                translatedObj[category] = [];
            }
            setOfWords.forEach(function(word){
                google.translate(word, 'es', 'en').then(function(translation){
                    translatedObj[category].push(translation.translatedText);
                });
            });

            // return the translatedObj until all of the categories are translated
            resolve(translatedObj);
        });
    });
}

You can use Promise.all() to wait for all promise fulfillments (or first rejection)

var translateRequests = [];

Object.keys(setOfWords).forEach(function(category){
        setOfWords[category].forEach(function(word){
            translateRequests.push(google.translate(word, 'es', 'en'));
        });
    });
});

Promise.all(translateRequests).then(function(translateResults){
    //do something with all the results
});

See docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Individual promises need to be aggregated with Promise.all() and fundamentally, that's what's missing.

But you can do better by reducing the number of calls to the Google service.

The Google translate API allows multiple text strings to be translated in one hit by passing an array of words instead of one word per call, giving you a performance advantage though probably not price advantage - google currently charges for its translation service "per character", not "per call".

I can't find any documentation for google.translate() but, with a few assumptions, you may be able to write :

function translateByCategory(obj, sourceCode, targetCode) {
    let translatedObj = {};
    var promises = Object.keys(obj).map(function(key) {
        return google.translate(obj[key], sourceCode, targetCode).then(function(translations) {
            translatedObj[key] = translations.map(function(t) {
                return t.translatedText || '-';
            });
        }, function(error) {
            translatedObj[key] = [];
        });
    });
    return Promise.all(promises).then(function() {
        return translatedObj;
    });
}

If that doesn't work, then this documentation explains how to call google's RESTful translation service directly.

You should be able to write :

function translateTexts(baseParams, arrayOfStrings) {
    let queryString = baseParams.concat(arrayOfStrings.map(function(str) {
        return 'q=' + encodeURIComponent(str);
    })).join('&');

    return http.ajax({ // some arbitrary HTTP lib that GETs by default.
        url: 'https://translation.googleapis.com/language/translate/v2?' + queryString,
    }).then(function(response) {
        return response.data.translations.map(function(t) {
            return t.translatedText || '-';
        });
    }, function(error) {
        translatedObj[key] = []; // on error, default to empty array 
    });
}

function translateByCategory(obj, sourceCode, targetCode) {
    let baseParams = [
        'key=' + MY_API_KEY, // from some outer scope
        'source=' + sourceCode, // eg 'en'
        'target=' + targetCode // eg 'es'
    ];
    let translatedObj = {};
    let promises = Object.keys(obj).map(function(key) {
        return translateTexts(baseParams, obj[key]).then(function(translations) {
            translatedObj[key] = translations;
        }, function(error) {
            translatedObj[key] = []; // on error, default to empty array 
        });
    });
    return Promise.all(promises).then(function() {
        return translatedObj;
    });
}

In either case, call as follows :

let setOfWords = {
    "nouns": [
        "work",
        "construction",
        "industry"
    ],
    "verbs": [
        "work"
    ],
};

translateByCategory(setOfWords, 'en', 'es').then(function(setOfTranslatedWords) {
    console.log(setOfTranslatedWords);
});

The method suggested by @hackerrdave can be modified to make it more compatible with async await feature of JavaScript, do this like the following:

function translateByCategory(){

    return new Promise(function(resolve, reject){
        var translateRequests = [];
        Object.keys(setOfWords).forEach(function(category){
                setOfWords[category].forEach(function(word){
                    translateRequests.push(google.translate(word, 'es', 'en'));
            });
        });
        Promise.all(translateRequests).resolve(resolve(translateRequests));
    });
}

So now you can do something like:

let translatedObj = await translateByCategory();

And you will get what you want in "translatedObj".

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