Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
        });
    });
}
like image 289
Gus Avatar asked Nov 22 '16 01:11

Gus


2 Answers

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 the docs: Promise.all()

like image 108
hackerrdave Avatar answered Nov 03 '22 00:11

hackerrdave


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);
});
like image 28
Roamer-1888 Avatar answered Nov 03 '22 00:11

Roamer-1888