0

EDIT: currently i think the problem with this is that forEach is not promise aware. https://zellwk.com/blog/async-await-in-loops/

I am trying to apply a node javascript translation function (ive put it at the end of the post because it is quite long) to loop over an array of values. However when i loop for some reason i'm only getting certain parts of my looped function to appear after the loop has completed: Allow me to make this more clear:

array = [["hello", "hello" ],
         ["my", "my",  ],
         ["name", "name" ],
         ["is", "my" ],
         ["joe", "joe"]]

function process (item,index){
   
const translate = require('@vitalets/google-translate-api');
      
      console.log('loopaction'); //this shows that the loop is executing
    translate(item[0], {to: 'sp'}).then(result => {
    console.log(result.text);
  
}).catch(err => {
    console.error(err);
})


array.forEach(process);   // Applying the function process to the array in a ForEach loop

from this i am getting

loopaction loopaction loopaction loopaction loopaction

hola mi nombre es joe

So it seems that the forEach loop is completing before the values are being allowed to be displayed. Which is something i really don't understand since the array values are being translated correctly and then logged out in the correct order. As if they had been stored in the memory for later. And then called at the end of the forEach loop order.

The translate function looks like this:

function translate(text, opts, gotopts) {
opts = opts || {};
gotopts = gotopts || {};
var e;
[opts.from, opts.to].forEach(function (lang) {
    if (lang && !languages.isSupported(lang)) {
        e = new Error();
        e.code = 400;
        e.message = 'The language \'' + lang + '\' is not supported';
    }
});
if (e) {
    return new Promise(function (resolve, reject) {
        reject(e);
    });
}

opts.from = opts.from || 'auto';
opts.to = opts.to || 'en';
opts.tld = opts.tld || 'com';

opts.from = languages.getCode(opts.from);
opts.to = languages.getCode(opts.to);

var url = 'https://translate.google.' + opts.tld;
return got(url, gotopts).then(function (res) {
    var data = {
        'rpcids': 'MkEWBc',
        'f.sid': extract('FdrFJe', res),
        'bl': extract('cfb2h', res),
        'hl': 'en-US',
        'soc-app': 1,
        'soc-platform': 1,
        'soc-device': 1,
        '_reqid': Math.floor(1000 + (Math.random() * 9000)),
        'rt': 'c'
    };

    return data;
}).then(function (data) {
    url = url + '/_/TranslateWebserverUi/data/batchexecute?' + querystring.stringify(data);
    gotopts.body = 'f.req=' + encodeURIComponent(JSON.stringify([[['MkEWBc', JSON.stringify([[text, opts.from, opts.to, true], [null]]), null, 'generic']]])) + '&';
    gotopts.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

    return got.post(url, gotopts).then(function (res) {
        var json = res.body.slice(6);
        var length = '';

        var result = {
            text: '',
            pronunciation: '',
            from: {
                language: {
                    didYouMean: false,
                    iso: ''
                },
                text: {
                    autoCorrected: false,
                    value: '',
                    didYouMean: false
                }
            },
            raw: ''
        };

        try {
            length = /^\d+/.exec(json)[0];
            json = JSON.parse(json.slice(length.length, parseInt(length, 10) + length.length));
            json = JSON.parse(json[0][2]);
            result.raw = json;
        } catch (e) {
            return result;
        }

        if (json[1][0][0][5] === undefined) {
            // translation not found, could be a hyperlink?
            result.text = json[1][0][0][0];
        } else {
            json[1][0][0][5].forEach(function (obj) {
                if (obj[0]) {
                    result.text += obj[0];
                }
            });
        }
        result.pronunciation = json[1][0][0][1];

        // From language
        if (json[0] && json[0][1] && json[0][1][1]) {
            result.from.language.didYouMean = true;
            result.from.language.iso = json[0][1][1][0];
        } else if (json[1][3] === 'auto') {
            result.from.language.iso = json[2];
        } else {
            result.from.language.iso = json[1][3];
        }

        // Did you mean & autocorrect
        if (json[0] && json[0][1] && json[0][1][0]) {
            var str = json[0][1][0][0][1];

            str = str.replace(/<b>(<i>)?/g, '[');
            str = str.replace(/(<\/i>)?<\/b>/g, ']');

            result.from.text.value = str;

            if (json[0][1][0][2] === 1) {
                result.from.text.autoCorrected = true;
            } else {
                result.from.text.didYouMean = true;
            }
        }

        return result;
    }).catch(function (err) {
        err.message += `\nUrl: ${url}`;
        if (err.statusCode !== undefined && err.statusCode !== 200) {
            err.code = 'BAD_REQUEST';
        } else {
            err.code = 'BAD_NETWORK';
        }
        throw err;
    });
});
}

I realise there is a promise format and the problem im having may have to do with the asychronisity of the function and how the long the promise is taking to get resolved. I cant seem to figure out why the promise is not resolving or displaying after my forEach function is completely looped yet it seems to be saved correctly and in the correct order. Very odd.

Any ideas about what is it about the function translate() that is making this happen? Is there anyway i can rewrite my function process () to make sure that the translate functions resolved promise and the .then() in function process () is fully executed before moving on?

Maximilian Travis
  • 169
  • 1
  • 1
  • 9

1 Answers1

2

You are correct, you are using promises, so translate() will run asynchronously (in the background) while the rest of your code is executing. That is why you go through all the foreach() before the translate function returns, and therefore you get that output.

However, there is also a problem using a forEach loop in an async function or a promise block. The callback function is not being awaited. Therefore, the promise chain is broken, resulting in the unexpected behavior. Don't use forEach loop in a promise or async function. Instead, use a for loop to iterate through the items of the array:

To avoid these problems, change the forEach loop to a For loop and use async and await like this:

async function process (item,index){
    const translate = require('@vitalets/google-translate-api');
    console.log('loopaction'); //this shows that the loop is executing
    await translate(item[0], {to: 'sp'})
    .then(result => {
        console.log(result.text);
    })
    .catch(err => {
        console.error(err);
    })
}

async function main() {
    array = [["hello", "hello" ],
         ["my", "my" ],
         ["name", "name" ],
         ["is", "my" ],
         ["joe", "joe"]]
    
    for (let i = 0; i < array.length; i++) {
        await process(array[i], i);
    }
}

main()

await makes the function wait until the promise is resolved.

NOTE: You tried to create a timeout with object.sleep(), this doesn't exist in javascript, use setTimeout() instead, refer to: Sleep() in Javascript

Jesus Aguas
  • 225
  • 7