Неблокирующее асинхронное итерирование в JavaScript

Oct 19, 2019

Неблокирующий each

// после прохождения 10мс мы хотим отдать квант времени в event loop
const INTERVAL = 10;

const numbers = new Array(1000).fill(1);

const each = (array, fn) => {
  let time = Date.now();
  let i = 0;
  const last = array.length - 1;

  const next = () => {
    while (i <= last) {
      const now = Date.now();
      const diff = now - time;
      // проверяем сколько времени прошло до константы INTERVAL
      if (diff > INTERVAL) {
        time = now;
        // рекурсивно начинаем новый цикл
        setTimeout(next, 0);
        break;
      } else {
        // на большинстве итерациях будет вызываться while и ф-я ниже
        // и лишь на небольшой части будет вызываеться setTimeout,
        // чтобы отдать в event loop
        fn(array[i], i++);
      }
    }
  };

  next();
};

let k = 0;
// k здесь для примера, то есть смотря на нее мы видим сколько раз
// случится вывод k (вывод вне итер-го цикла, то есть освобожд. потока)
const timer = setInterval(() => {
    console.log('next ', k++);
}, 10);

const begin = process.hrtime.bigint();

each(numbers, (item, i) => {
  console.log(i);
  if (i === 999) {
    clearInterval(timer);
    const diff = (process.hrtime.bigint() - begin) / 1000000n;
    console.log('Time(ms):', diff.toString());
    console.dir({ k });
  }
});

Неблокирующий for

// суть таже, что и в предыдущем примере

// после прохождения 10мс мы хотим отдать квант времени в event loop
const INTERVAL = 10;

// range - асинхронно итерируемый объект
const range = {
    start: 1,
    end: 1000,
    [Symbol.asyncIterator]() {
        let time = Date.now();
        let value = this.start;
        return {
            next: () => {
                const now = Date.now();
                const diff = now - time;
                // проверяем сколько времени прошло до константы INTERVAL
                if (diff > INTERVAL) {
                    time = now;
                    return new Promise((resolve, reject) => {
                        setTimeout(() => {
                            resolve({
                                value,
                                done: value++ === this.end + 1
                            });
                        }, 0);
                    });
                } else {
                    // вызов Promise.resolve не подразумевает асинхронности
                    return Promise.resolve({
                        value,
                        done: value++ === this.end + 1
                    });
                }
            }
        };
    }
};

console.dir({
    range,
    names: Object.getOwnPropertyNames(range),
    symbols: Object.getOwnPropertySymbols(range),
});

let k = 0;

const timer = setInterval(() => {
    console.log('next ', k++);
}, 10);

(async () => {
  const begin = process.hrtime.bigint();
  for await (const number of range) {
    console.log(number);
    if (number === range.end) {
     clearInterval(timer);
    }
  }
  const diff = (process.hrtime.bigint() - begin) / 1000000n;
  console.log('Time(ms):', diff.toString());
  console.dir({ k });
})();

Неблокирующий for для массивов

const INTERVAL = 10;

class AsyncArray extends Array {
  interval(ms) {
    this._interval = ms;
    return this;
  }
  [Symbol.asyncIterator]() {
    let time = Date.now();
    let i = 0;
    const interval = this._interval || INTERVAL;
    return {
    next: () => {
        const now = Date.now();
        const diff = now - time;
        if (diff > interval) {
          time = now;
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({
                value: this[i],
                done: i++ === this.length
              });
            }, 0);
          });
        } else {
          return Promise.resolve({
            value: this[i],
            done: i++ === this.length
          });
        }
      }
    };
  }
}

// Usage

let k = 0;

const timer = setInterval(() => {
    console.log('next ', k++);
}, 10);

(async () => {
    const numbers = new AsyncArray(1000)
      .interval(10)
      .fill(1);

  const begin = process.hrtime.bigint();
  let i = 0;
  for await (const number of numbers) {
    console.log(number, i++);
  }
  clearInterval(timer);

  const diff = (process.hrtime.bigint() - begin) / 1000000n;
  console.log('Time(ms):', diff.toString());
  console.dir({ k });
})();

Источник github.com/HowProgrammingWorks/NonBlocking

Добавить комментарий