#javascript #nodejs #async_programming #promise
Задача: собираю данные геодаты с сервера (не моего). Для этого формирую запрос для определенного промежутка координат и в цикле 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'ом знакомство только начинаю, поэтому если кроме ответа еще и посоветуете где и как лучше ознакамливаться буду только рад.
Ответы
Ответ 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Ответ 2
Собери промисы в массив в цикле потом дождись их выполнения используя Promise.all
Комментариев нет:
Отправить комментарий