Для чего используют AsyncIterator в JavaScript

В MDN есть статья про AsyncIterator, однако, технология кажется весьма узкоспециализированной. Не понятен кейс использования: задачи, которые она решает, значительно более читаемым методом будет реализовать через Promise.all или с помощью цикла с оператором await в теле

mdn-asynciterator

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


Ответы (1 шт):

Автор решения: Трипольский Пётр

Используя AsyncIterator можно эффективно обойти базу данных, не выгружая ее в оперативную память. Например, используя Promise.all нам потребуется хранить единовременно в оперативной памяти массив всех id-шников из таблиц, может не хватить места

const listDocuments = async function* (databaseId, collectionId, queries = []) {

  let lastId = null;

  const createRequest = async () => {
    console.log(`Fetching collection ${collectionId} from ${collectionId} starting from ${lastId || 0}`)
    return await databases.listDocuments(
      databaseId,
      collectionId,
      [
        sdk.Query.orderDesc("$updatedAt"),
        ...(lastId ? [sdk.Query.cursorAfter(lastId)] : []),
        ...queries,
        sdk.Query.limit(DOCUMENTS_PAGE_SIZE),
      ]
    );
  };

  let counter = 0;
  let lastQuery = createRequest();

  while (counter < TOTAL_DOCUMENTS_LIMIT) {
    const [{ documents }] = await Promise.all([
      lastQuery,
      sleep(DOCUMENT_READ_DELAY),
    ]);
    for (const document of documents) {
      yield document;
    }
    if (documents.length < DOCUMENTS_PAGE_SIZE) {
      break;
    }
    lastId = documents[documents.length - 1].$id;
    lastQuery = createRequest();
    counter += documents.length;
  }
};

Данный код позволяет считывать данные из базы без индексов циклом с предусловием, однако, запрос выполняется в фоне вместе с фильтрами строк в синхронном виде. Помимо этого, можно установить задержку так, чтобы один запрос выполнялся не менее чем раз в секунду, если есть ограничение на балансировщике. Примечательно, что задержка высчитывается на момент начала запроса, время исполнения запроса не учитывается

const createPaginate = (limit, offset) => {
  const result = [];
  return (rows = []) => {
    for (const row of rows) {
      if (offset > 0) {
        offset -= 1;
        continue;
      }
      if (limit > 0) {
        result.push(row);
        limit -= 1;
        continue;
      }
      break;
    }
    return {
      rows: result,
      done: limit <= 0,
    };
  }

};

...


const paginate = createPaginate(25, 0)

...


for await (let rows of repository.listDocuments(request)) {

  rows = rows.filter(({ price }) => price > filterData.priceFrom)
  rows = rows.filter(({ price }) => price < filterData.priceTo)

  ...

  if (paginate(rows).done) {
    break;
  }
}

const { rows } = paginate();
response.json(rows);

Ограничения по limit и offset позволяют сборщику мусора планово выгружать не используемые данные. Код академического примера взят из этого репозитория. Крайне полезно, если нужно реализовать фильтр, написать который SQL запросом технически сложно

→ Ссылка