gzhegow
@gzhegow
aka "ОбнимиБизнесмена"

Как правильно написать синхронную ожидалку на JS?

Написал я вот такой код:
var show_timer = false;
var waits = {};
var default_limit_burst = 12;
var default_limit_minute = 200;
var limit_burst = 12;
var limit_minute = 200;
function wait(name, timeout) {
  let obj = {};

  // init
  waits[name] = waits[name] || {};
  waits[name].irv = waits[name].irv || [];
  waits[name].ps = waits[name].ps || [];

  // create interval
  obj.promise = new Promise((resolve, reject) => {
    obj.left = timeout;
    obj.interval = setInterval(() => {
      obj.left--;
      if (show_timer) console.log('Timer `' + name + '`. Left: ' + obj.left);
      if (obj.left <= 0) {
        clearInterval(obj.interval);
        resolve([null, 'OK']);
      }
    }, 1200);
  }).catch(frpd);

  // get old keys
  var keys = array_keys(waits[name].ps) || [];

  // save new items
  waits[name].irv.push(obj.interval);
  waits[name].ps.push(obj.promise);

  // try to resolve all old promises/intervals except current
  try {
    keys.forEach(v => {
      clearInterval(waits[name].irv[v]);
      Promise.resolve(waits[name].ps[v]);
    });
  } catch (e) {
    frpd(e);
  }

  // return batch promise
  return Promise.all(waits[name].ps);
}


Сколько бы я раз не вызвал wait('timer1', 5) - он должен складывать все вместе в один массив промисов и выдаст результат только когда все закончатся, ну как я предполагал.

Однако наблюдается забавный эффект, а именно когда заканчиваются таймеры - почему-то программа выпадает в process.exit() - заканчивается весь скрипт, хотя запросов еще много впереди.

В коде пишется так

co(function* () {
  // ждем 5 сек
  if (limit < 0)  yield wait('timer1', 5);
});


Прикол не в том чтобы просто в коде подождать для этого можно написать
yield new Promise(resolve => setTimeout(resolve, 5000));


Прикол в том, что некоторые запросы делаются пачками, так называемые batch запросы.
И в пачке далеко не всегда limit запросов! Там может быть например 12 запросов, а лимит сейчас - всего 5. Таким образом нужно сделать 5, а когда таймер резолвнется - сделать остальные 7.

Там получится
var ps = [];
for (let i in requests) {
  ps.push(
   co(function* () {
     if (limit < 0)  yield wait('timer1', 5);    
   });
  );
}
return Promise.all(ps);


В данном случае запускается batch таймеров. И закончится они должны все вместе, т.к. один таймер тут не запустишь мне кажется.

Поправьте пожалуйста мой код или направьте в нужном направлении, может быть логика изначально не так.
  • Вопрос задан
  • 629 просмотров
Пригласить эксперта
Ответы на вопрос 1
gzhegow
@gzhegow Автор вопроса
aka "ОбнимиБизнесмена"
Пока заработало вот так:
вместо того чтобы запускать таймер для каждого запроса в пачке написал вот такую примерно конструкцию:
co(function* () {
  var limit_burst = default_limit_burst;
  var limit_minute = default_limit_minute;
  var limit_day = default_limit_day;

  // getting records from somewhere
  var records = get_the_records();

  records = yield co(function* () {
    var records_done = [];
    while (records.length) {
      let limit = Math.min(limit_burst, limit_minute, limit_day);
      let batch = records.splice(0, limit);
      
      // update limits
      limit_burst -= batch.length;
      limit_minute -= batch.length;
      limit_day -= batch.length;

      for (let i in batch) {
        // ... request
      }

      // refresh limits from response headers if available
      yield Promise.all(ps).then(results => {
        var current_limit_burst = default_limit_burst;
        var current_limit_minute = default_limit_minute;
        var current_limit_day = default_limit_day;

        // save results
        records_done = results.map(r => {
          if (r[0] !== null) return console.error(r[1]) || null;
          current_limit_burst = Math.min(current_limit_burst, r[2][0]);
          current_limit_minute = Math.min(current_limit_minute, r[2][1]);
          current_limit_day = Math.min(current_limit_day, r[2][2]);
          return r[1];
        }).filter(r => r!==null);

        limit_burst = current_limit_burst;
        limit_minute = current_limit_minute;
        limit_day = current_limit_day;
      });

      if (limit_burst <= 0) yield new Promise(r => setTimeout(r, 5000));
      if (limit_minute <= 0) yield new Promise(r => setTimeout(r, 60000));
      if (limit_day <= 0) yield new Promise(r => setTimeout(r, 86400000));
    }

    return records_done;
  });
});
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы