Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Асинхронность, Promise, Callback, onload (https://javascript.ru/forum/misc/82058-asinkhronnost-promise-callback-onload.html)

Stanislavsonder 06.03.2021 21:59

Асинхронность, Promise, Callback, onload
 
Добрый день! Пишу несколько решений и столкнулся в принципе с однотипной проблемой и там и там, но опишу задачи раздельно.

Задача 1. Node.js, Парсинг.
Необходимо пробежаться по некоторым URL и сохранить информацию с них в JSON файл. Все это происходит как-то так:
var request = require ('request');
var cheerio = require ('cheerio');
const fs = require('fs');

var results = new Array();
var pagesCount = 47;
for (let i = 0; i < pagesCount; i++){
	var URL = 'здесь ссылка плюс ид страницы='+i;
	request(URL, async function (error, response, html) {
	  if (!error && response.statusCode == 200) {
	    var $ = cheerio.load(html);
	    $('tr:not(.spacer)').each(function(i, element){
	     // Тут происходит много строчек кода селекторов и записи в объект data.
	      console.log(data);
	      	results.push(data);
		});
	  	}

		});
	}

// В конце мне все это нужно сохранить в JSON, То есть записать массив result

setTimeout(()=> {
	console.log('saving...');
	fs.writeFileSync("data.json", JSON.stringify(results,null,4));
},10000)



Проблема заключается в том, что 10000 (или 10 сек) это асинхронный костыль, который работает только на моей машине как надо. Если запустить это на слабом сервере - нужно будет прощупывать уже другую задержку. Как сделать так, чтобы сохранение произошло по готовности? И желательно с пояснением.
P.S. Если просто написать без timeOut, то он сохранит массив в момент начала парсинга. В итоге у меня пустой файл и запуск парсера.


Задача 2. onload для массива изображений

Есть canvas. В нем отрисовываются изображения как паттерны. Их зачастую 12 штук. Можно менять количество, не суть.
Изображения берутся с внешнего сервера и им нужно время на подзагрузку. После того как они загрузятся необходимо перерисовать canvas.

Вопрос, как проверить .onload для массива изображений, чтобы загрузились они все. Сейчас код выглядит примерно так, костыль тот же, что и раньше:

function totalUpdate() {
    setGamesForRoll();
    setTimeout(setImagesForRoll,0);
    setTimeout(drawWhell, 800);
}
function setImagesForRoll() {
    for (let i = 0; i < rollConfig.sections; i++){
    sections[i] = games[i].name;
    img[i] = new Image();
    img[i].src = games[i].imagesrc +'?rand=' + Math.random();
    img[i].width = '100px';
    img[i].height = '100px';
    }
}

Задержки в 0.8 секунд тоже не хватает при нестабильном интернет-подключении клиента.

рони 06.03.2021 22:25

Цитата:

Сообщение от Stanislavsonder
Задача 2. onload для массива изображений

<!DOCTYPE html>

<html>
<head>
    <title>Untitled</title>
    <meta charset="utf-8">
    <style type="text/css">
    </style>

    <script>
document.addEventListener( "DOMContentLoaded" , function() {
  "use strict"
let imgs = [
    'https://pbs.twimg.com/profile_images/717294044922187776/ojsz7uPz_400x400.jpg',
    'https://www.florissimo-shop.ru/upload/resize_cache/iblock/266/256_256_1/4100.jpeg',
    'https://cdn.shortpixel.ai/client/q_glossy,ret_img,w_256/https://iris32.ru/wp-content/uploads/s1200-256x256.jpg',
    'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRoTiQSapkMQDacdDcRC9L0OOzVYYT1HuyWTw&usqp=CAU',
    'https://ledforms.com/1906-medium_default_2x/10500120.jpg',
    'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ-KiKgpbTbFqKysnND1YAdMJ0qUSSa7bbUvA&usqp=CAU',
    'https://open3dmodel.com/wp-content/uploads/2020/03/Flowers-rose.jpg'
  ]

var ins = [];
imgs = imgs.map(src => {
  let img = new Image();
  img.src = src;
  ins.push(img);

  return new Promise(resolve => {
    img.onload = resolve
  })
})

Promise.all(imgs).then(function() {
    slider.prepend(...ins)//canvas
});
  });
    </script>
</head>

<body>
<div id="slider"></div>

</body>
</html>

voraa 06.03.2021 22:27

С картинками так
async function totalUpdate() {
    setGamesForRoll();
    await setImagesForRoll();
    drawWhell()
}

async function setImagesForRoll() {
	let imload = []; // массив обещаний
    for (let i = 0; i < rollConfig.sections; i++){
		sections[i] = games[i].name;
		img[i] = new Image();
		img[i].src = games[i].imagesrc +'?rand=' + Math.random();
		img[i].style.width = '100px';
		img[i].style.height = '100px';
		imload.push(img[i].decode())
	}
	await Promise.all(imload)  // ждем, когда все загрузится
}

Stanislavsonder 06.03.2021 22:45

Спасибо большое, работает как часы. Вариант Рони тоже подходит.
Нужно постигать глубины асинхронности в JS(

voraa 06.03.2021 22:53

Первую задачу можно так сделать
var request = require ('request');
var cheerio = require ('cheerio');
const fs = require('fs');
 
var results = new Array();
var pagesCount = 47;

let prm = Promise.resolve();

for (let i = 0; i < pagesCount; i++){
    var URL = 'здесь ссылка плюс ид страницы='+i;
    prm = prm.then ( _ => new Promise (res => {
			request(URL, function (error, response, html) {
			  if (!error && response.statusCode == 200) {
					var $ = cheerio.load(html);
					$('tr:not(.spacer)').each(function(i, element){
					 // Тут происходит много строчек кода селекторов и записи в объект data.
					  console.log(data);
					  results.push(data);
					});
					res();
				}
		 
			});
		})
    )
}
 
// В конце мне все это нужно сохранить в JSON, То есть записать массив result
 
prm.then (_=> {
    console.log('saving...');
    fs.writeFileSync("data.json", JSON.stringify(results,null,4));
})

Stanislavsonder 06.03.2021 23:17

voraa, При такой реализации он немного ожидает а потом в лог мне кидает одну и ту же страницу скорее всего все 47 раз. В массив и json Тоже идет повторение одной и той же страницы информации. Как-будто бы цикл не инкрементирует.

voraa 06.03.2021 23:37

Попробуй
let URL = 'здесь ссылка плюс ид страницы='+i;

рони 06.03.2021 23:39

Stanislavsonder,
а если var на let поменять -- строка 11, пост #5

рони 06.03.2021 23:40

voraa,
:)

Stanislavsonder 07.03.2021 00:50

Цитата:

Сообщение от рони
а если var на let поменять -- строка 11, пост #5

Цитата:

Сообщение от voraa
let URL = 'здесь ссылка плюс ид страницы='+i;

Теперь он делает очень странные задержки по секунд 10 между итерациями, всего сделал 10 итераций и потом закончил работу, не сохранив ничего)))

Если нужно, могу кинуть полный код.

voraa 07.03.2021 08:24

Цитата:

Сообщение от Stanislavsonder
Теперь он делает очень странные задержки по секунд 10 между итерациями, всего сделал 10 итераций и потом закончил работу, не сохранив ничего)))

Просто закончил не выдав никакого сообщения об ошибке? Я нодой не пользуюсь, поэтому точно не знаю, но думаю она должна какие то месажы в консоль давать.

Раньше они могли выполняться параллельно, а тут я их последовательно запустил.
Возможно, что при обращении к странице произойдет какая то ошибка? Что в этом случае надо делать? Просто не помещать data в results?
И еще вопрос. Имеет ли значение, что в result данные должны быть именно в той последовательности, как URL (в зависимости от i)?
Ведь при параллельной обработке не гарантируется, что страница, запрошенная раньше, будет раньше и обрабатываться. Как считается так и обработается. А если использовать results.push(data), то данные в results будут идти той последовательности, как страницы считались и обработались.
Пока попробуй такой код. Тут страницы снова считываются параллельно, и есть некоторая обработка возможных ошибок в виде сообщений.

var request = require ('request');
var cheerio = require ('cheerio');
const fs = require('fs');
  
var results = new Array();
var pagesCount = 47;
 
let proms = []; 
for (let i = 0; i < pagesCount; i++){
    proms.push( new Promise ((res, rej) => {
			let URL = 'здесь ссылка плюс ид страницы='+i;
            request(URL, function (error, response, html) {
              if (!error && response.statusCode == 200) {
                    let $ = cheerio.load(html);
                    $('tr:not(.spacer)').each(function(i, element){
                     // Тут происходит много строчек кода селекторов и записи в объект data.
                      console.log(data);
                      results.push(data);
                    });
                    res();
                } else {
                    console.log(`Error ${error}, url: ${URL}, statusCode: ${response?.statusCode}`);
					rej (error);
                }
          
            });
        })
   )
}
  
// В конце мне все это нужно сохранить в JSON, То есть записать массив result
  
Promise.allSettled(proms).then (_=> {
    console.log('saving...');
    fs.writeFileSync("data.json", JSON.stringify(results,null,4));
});

Stanislavsonder 07.03.2021 12:44

voraa, нет, для результата нет разницы в последовательности обработанных страниц, параллельность тут только приветствуется. Поэтому я раньше именно так и обходил все страницы.
Нода выдает сообщения об ошибках, но обычно синтаксические больше. Там все было без ошибок.

Новый код работает как надо, каждый раз выдает одно и то же на выходе и нет сообщений об ошибке)

Спасибо, думаю задачку можно считать решенной)


Часовой пояс GMT +3, время: 04:36.