Использование promise с циклом for

96
13 декабря 2021, 03:50

Задача: собираю данные геодаты с сервера (не моего). Для этого формирую запрос для определенного промежутка координат и в цикле for отправляю запрос на сервер. Ответ записываю в файл и дабы сервер не банил меняю ip адрес через tor-control. Ожидание ответа реализовано с использованием promise. Это в идеале. На деле (судя по логам в консоли) данные отправляются как попало. Подскажите где я повернул не туда.

for (var i = xMin; i <= xMax; i++) {
    for (var k = yMin; k <= yMax; k++) {
        var swp = swPoint(k, i);
        var nep = nePoint(k, i);
        var llsw = pointToLatLong(swp[0], swp[1]);
        var llne = pointToLatLong(nep[0], nep[1]);
        var datatosend = 'fromlat=' + llsw[0].toString() + 'tolat=' + llne[0].toString() + 'fromlng=' + llsw[1].toString() + 'tolng' + llne[1].toString();
        currX = k;
        currY = i;
        filename = "json/" + currX.toString() + "_" + currY.toString() + "_" + zoom.toString() + ".json";
        options.body = datatosend;
        console.log(filename);
        GetData(options)
        .then(body =>{
            return WriteToFile(body, filename);
        })
        .then(() =>{
            return GetNewCircuit();
        })
        .then(() =>{
            console.log('Step finished');
        })
    }
}

Судя по логам получается что он 4 раза проносится по циклу не дожидаясь выполнения предыдущих операций.

Текст лога консоли:

json/0_0_1.json
json/1_0_1.json
json/0_1_1.json
json/1_1_1.json
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished
Get response
json/1_1_1.json
Circuit changed
Step finished

Вызываемые методы:

function GetNewCircuit(){
    return new promise(function(resolve, reject){
        new_identity('127.0.0.1', 9051, cookie, function(err){
            if(!err){
               console.log('Circuit changed');
               resolve()
            } else{
                console.log(err);
                reject(err);
            }
        });
    });
}
function GetData(reqOptions){
    return new promise(function(resolve, reject){
        request(reqOptions, function(error,response,body){
            if(!error && response.statusCode == 200){
                console.log('Get response');
                resolve(body);
            }
            else{
                reject(error);
            }
        });
    });
}
function WriteToFile(dataToWrite, fileToWrite){
    return new promise(function(resolve, reject) {
        fs.writeFile(fileToWrite, dataToWrite, function(err) {
           if(!err){
               console.log(fileToWrite);
               resolve();
           }
           else{
               reject(err);
           }
        }); 
    });
}

С js'ом знакомство только начинаю, поэтому если кроме ответа еще и посоветуете где и как лучше ознакамливаться буду только рад.

Answer 1

В коде этом сразу две основных проблемы (из трех) начинающих javascript-разработчиков вижу я.

Проблема один - непонимание асинхронности.
Разумеется, цикл не ждет возврата результата асинхронной операции, чтобы прокрутиться дальше.

Представьте что у вас четыре гиперактивных двортерьера, и вы подряд кидаете им четыре мячика. В каком порядке они их принесут обратно? Да черт его знает.

А то что внутри Promise.then, это колбек, только написанный удобнее.

Что делать?
Не надо:
Первое решение которое приходит в голову, а давайте дождемся конца первой операции, а потом уже счетчик цикла увеличим и в следующей итерации запустим следующую операцию. Но этим Вы просто превращаете асинхронную операцию в синхронную и теряете весь профит от асинхронности.
Надо:
Понять что основная проблема этого кода не в асинхронности, а в том что Вы не понимаете замыкания. Ну то есть вас не смущает что у вас консольложится json/1_1_1.json столько раз?

Проблема два:
непонимание областей видимости в js, они же замыкания. Вопрос про потерю значения переменной в цикле - топ 1 вопрос на собеседованиях на мидл разработчика, при неответе на который разговор можно сворачивать. На этом ресурсе этот вопрос в разных формулировках встречается пару раз в неделю. Например вот здесь на него подробно ответили (не упоминая block scope в es6 правда).

Ну то есть ядро ошибки в вашем коде выглядит как-то так:

for(var i = 0; i<4; i++){
    setTimeout(function(){console.log(i)}, 1000)
}

setTimeout тут - как пример простейшей асинхронной операции. Если Вы не понимаете почему тут выведет 4 раза 4 - надо медитировать пока не поймете. Обязательно.
Если вкратце, то это происходит потому что скоуп в js для var переменных - это функция, а не фигурные скобочки. Цикл отдельного скоупа не образует и все четыре раза функция будет ссылаться на одну и ту же переменную i, на одну и ту же область памяти.

Ок, я все понял, все равно хочу синхронно.
Для того чтобы хитро управлять ходом управления множества асинхронных запросов есть ряд распространенных библиотек, например async, посмотрите например на async.waterfall

Answer 2

Собери промисы в массив в цикле потом дождись их выполнения используя Promise.all

READ ALSO
Помогите установить javascript на сайт

Помогите установить javascript на сайт

В разработке сайта, начинающийНе могу подцепить javascript к шаблону сайта

117
Как сделать открытие следующего блока из квадрата, расположенного в первом блоке?

Как сделать открытие следующего блока из квадрата, расположенного в первом блоке?

Разместил вот здесь всю реализациюВ каком направлении думать, чтобы реализовать раскрытие нового блока как-бы из квадрата, который расположен...

69
Как выбрать и отредактировать нужные элементы в нескольких &#39;li&#39;?

Как выбрать и отредактировать нужные элементы в нескольких 'li'?

У меня есть список , в котором будет неизвестное количество 'li'В каждой li будут всегда одинаковые элементы,которые должны редактироваться...

118
Как сгруппировать элементы массива на javascript

Как сгруппировать элементы массива на javascript

Всем приветДопустим у нас есть массив элементов со свойствами name и ID

144