Loading...

Асинхрондук итераторлор жана генераторлор

Асинхрондук итераторлор жана генераторлор

 

Асинхрондук итераторлор асинхрондук түрдө келген маалыматтарды кайталоого мүмкүндүк берет. Мисалы, биз тармак аркылуу бир нерсени бөлүк-бөлүк жүктөгөндө. Асинхрондук генераторлор бул издөөнү дагы да ыңгайлуу кылат.

Синтаксисти түшүнүү үчүн адегенде жөнөкөй мисалды, андан кийин чыныгы практикалык мисалды карап көрөлү.

Асинхрондук итераторлор

Асинхрондук итераторлор кадимки итераторлорго окшош, бирок кээ бир синтаксистик айырмачылыктары бар.

"Кадимки" кайталануучу, Iterables бөлүмүндө айтылгандай , төмөнкүдөй көрүнөт:

 
 
@A@let range = {
  from: 1,
  to: 5,

  // for..of вызывает этот метод один раз в самом начале
  [Symbol.iterator]() {
    // ...возвращает объект-итератор:
    // далее for..of работает только с этим объектом, запрашивая следующее значение вызовом next()
    return {
      current: this.from,
      last: this.to,

      // next() вызывается на каждой итерации цикла for..of
      next() { // (2)
        // должен возвращать значение в виде объекта {done:.., value :...}
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for(let value of range) {
  alert(value); // 1, потом 2, потом 3, потом 4, потом 5
}@A@

Зарыл болсо, кадимки итераторлор жөнүндө көбүрөөк маалымат алуу үчүн итераторлор бөлүмүн караңыз .

Объектти асинхрондук түрдө кайталануучу кылуу үчүн:

  1. Symbol.asyncIteratorордуна колдонулат Symbol.iterator.
  2. next()убадасын кайтарышы керек.
  3. Мындай объектти кайталоо үчүн цикл колдонулат for await (let item of iterable).

rangeКелгиле , мурунку мисалдагыдай кайталануучу түзөлү , бирок азыр ал асинхрондуу түрдө секундасына бир маанини кайтарат:

@A@let range = {
  from: 1,
  to: 5,

  // for await..of вызывает этот метод один раз в самом начале
  [Symbol.asyncIterator]() { // (1)
    // ...возвращает объект-итератор:
    // далее for await..of работает только с этим объектом,
    // запрашивая у него следующие значения вызовом next()
    return {
      current: this.from,
      last: this.to,

      // next() вызывается на каждой итерации цикла for await..of
      async next() { // (2)
        // должен возвращать значение как объект {done:.., value :...}
        // (автоматически оборачивается в промис с помощью async)

        // можно использовать await внутри для асинхронности:
        await new Promise(resolve => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {

  for await (let value of range) { // (4)
    alert(value); // 1,2,3,4,5
  }

})()@A@

Көрүнүп тургандай, структурасы жөнөкөй итераторлорго окшош:

  1. Объектти асинхрондук түрдө кайталануучу кылуу үчүн, анда Symbol.asyncIterator (1).
  2. Бул ыкма объектти методу менен кайтарышы керек next(), ал өз кезегинде убаданы кайтарат (2).
  3. Метод next()болбошу керек async, ал убаданы кайтарган кадимки ыкма болушу мүмкүн, бирок asyncал сизге колдонууга мүмкүндүк берет await, ошондуктан бул ыңгайлуу. Бул жерде биз жөн гана бир секунд тыныгуу (3).
  4. Итерация үчүн биз for await (let value of range) (4)"үчүн" дегенден кийин "күтүү" дегенди кошобуз. Ал бир жолу чакырат range[Symbol.asyncIterator](), андан кийин next()баалуулуктарды алуу үчүн анын ыкмасы.

Бул жерде бир аз алдамчылык баракчасы:

  Итераторлор Асинхрондук итераторлор
Кайталануучу объектти түзүү ыкмасы Symbol.iterator Symbol.asyncIterator
next()кайтып келет кандайдыр бир баалуулук убада
цикл колдонуу үчүн for..of for await..of
таралган билдирүү ...асинхрондуу иштебейт

Кадимки синхрондук итераторлорду талап кылган функциялар асинхрондуктар менен иштебейт.

Мисалы, кеңейтүү оператору (үч чекит ...) иштебейт:

alert( [...range] ); // Ошибка, нет Symbol.iterator

Бул табигый нерсе, анткени ал күтөт Symbol.iterator, ошондой эле for..ofжок await. Ага туура келбейт Symbol.asyncIterator.

Асинхрондук генераторлор

Биз билгендей, JavaScriptтин генераторлору бар жана алар кайталануучу.

Генераторлор бөлүмүндөгү ырааттуулук генераторун эстеп көрөлү . startАл төмөнкүдөй маанилердин ырааттуулугун жаратат end:

 
 
@A@function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

for(let value of generateSequence(1, 5)) {
  alert(value); // 1, потом 2, потом 3, потом 4, потом 5
}@A@

Кадимки генераторлордо биз колдоно албайбыз await. Бардык баалуулуктар синхрондуу келиши керек: for..ofкечигүү үчүн орун жок, бул синхрондуу түзүлүш.

awaitБирок биз денеге генератор колдонуу керек болсо, эмне кылышыбыз керек ? Мисалы, тармактык суроо-талаптарды аткаруу үчүн.

Эч кандай көйгөй жок, жөн гана башында asyncтөмөнкүдөй кошуңуз:

 
 
@A@async function* generateSequence(start, end) {

  for (let i = start; i <= end; i++) {

    // ура, можно использовать await!
    await new Promise(resolve => setTimeout(resolve, 1000));

    yield i;
  }

}

(async () => {

  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1, потом 2, потом 3, потом 4, потом 5
  }

})();@A@

Азыр бизде итерацияланган асинхрондук генератор бар for await ... of.

Бул чынында эле абдан жөнөкөй. Биз ачкыч сөздү кошобуз async, эми сиз генератордун ичинде await, ошондой эле убадаларды жана башка асинхрондук функцияларды колдоно аласыз.

Техникалык көз караштан алганда, асинхрондук генератор менен дагы бир айырмачылык, анын ыкмасы generator.next()азыр да асинхрондуу жана убадаларды кайтарып берет.

Кадимки генератордон биз колдонуп баалуулуктарды ала алабыз result = generator.next()awaitАсинхрондук үчүн, төмөнкүдөй кошуу керек :

result = await generator.next(); // result = {value: ..., done: true/false}

Асинхрондук түрдө кайталануучу объекттер

Белгилүү болгондой, объектти кайталануучу кылуу үчүн кошуу керек Symbol.iterator.

let range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    return <объект с next, чтобы сделать range перебираемым>
  }
}

Кадимки практика мурунку мисалдагыдай Symbol.iteratorжөнөкөй объектти эмес, генераторду кайтаруу болуп саналат .next

Генераторлор бөлүмүндөгү бир мисалды эстейли :

@A@let range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() { // сокращение для [Symbol.iterator]: function*()
    for(let value = this.from; value <= this.to; value++) {
      yield value;
    }
  }
};

for(let value of range) {
  alert(value); // 1, потом 2, потом 3, потом 4, потом 5
}@A@

Бул жерде түзүлгөн объект rangeкайталануучу жана генератор *[Symbol.iterator]баалуулуктарды санап чыгуу логикасын ишке ашырат.

Эгерде генераторго асинхрондук аракеттерди кошкубуз келсе, анда аны Symbol.iteratorасинхрондук менен алмаштыруу керек Symbol.asyncIterator

@A@let range = {
  from: 1,
  to: 5,

  async *[Symbol.asyncIterator]() { // то же, что и [Symbol.asyncIterator]: async function*()
    for(let value = this.from; value <= this.to; value++) {

      // пауза между значениями, ожидание
      await new Promise(resolve => setTimeout(resolve, 1000));

      yield value;
    }
  }
};

(async () => {

  for await (let value of range) {
    alert(value); // 1, потом 2, потом 3, потом 4, потом 5
  }

})();@A@

Эми баалуулуктар алардын ортосунда бир секунд кечигүү менен келет.

Чыныгы дүйнө мисал

Буга чейин биз негизги түшүнүк алуу үчүн жөнөкөй мисалдарды көрдүк. Эми чыныгы абалды карап көрөлү.

Маалыматтарды барак менен камсыз кылган көптөгөн онлайн кызматтар бар. Мисалы, бизге колдонуучулардын тизмеси керек болгондо, суроо колдонуучулардын алдын ала аныкталган санын (мисалы, 100) - "бир барак" жана кийинки беттин URL дарегин кайтарат.

Бул ыкма абдан кеңири таралган жана бул жөн гана колдонуучуларга эмес, бардык нерсеге байланыштуу. Мисалы, GitHub сизге ушундай жол менен баракчаларды алууга мүмкүндүк берет:

  • түрүндөгү URL үчүн сурам жөнөтүшүңүз керек https://api.github.com/repos/<repo>/commits.
  • Жооп 30 милдеттендирилген JSON, ошондой эле башындагы кийинки бетке шилтеме болот Link.
  • Андан кийин сиз ошол шилтемени кийинки суроо-талапка көбүрөөк милдеттенмелерди алуу үчүн колдоно аласыз ж.б.у.с.

Бирок, албетте, бул татаал өз ара аракеттенүүнүн ордуна, биз итерациялануучу милдеттенмелери бар объектке ээ болгубуз келет, мисалы:

@A@let repo = 'javascript-tutorial/en.javascript.info'; // репозиторий на GitHub, откуда брать коммиты

for await (let commit of fetchCommits(repo)) {
  // обработка коммитов
}@A@

fetchCommits(repo)Биз зарыл болгон учурда суроо-талаптарды берүү менен милдеттенмелерди ала турган функцияны жасагыбыз келет . Ал барактоо менен байланышкан нерселердин баарын чечсин, биз үчүн бул жөнөкөй болот for await..of.

Асинхрондук генераторлор менен муну ишке ашыруу абдан оңой:

@A@async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`;

  while (url) {
    const response = await fetch(url, { // (1)
      headers: {'User-Agent': 'Our script'}, // GitHub требует заголовок user-agent
    });

    const body = await response.json(); // (2) ответ в формате JSON (массив коммитов)

    // (3) Ссылка на следующую страницу находится в заголовках, извлекаем её
    let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
    nextPage = nextPage && nextPage[1];

    url = nextPage;

    for(let commit of body) { // (4) вернуть коммиты один за другим, до окончания страницы
      yield commit;
    }
  }
}@A@
  1. Алыскы URL'ден жүктөө үчүн браузердин алып келүү ыкмасын колдонобуз . Бул сизге уруксатты жана башка аталыштарды кошууга мүмкүндүк берет, бул жерде GitHub талап кылат User-Agent.
  2. Натыйжа fetchJSON катары талданат, кайра fetch.
  3. Кийинки беттин URL дарегин жооп башынан алышыбыз керек Link. Анын өзгөчө форматы бар, ошондуктан биз кадимки сөз айкашын колдонобуз. Кийинки барактын URL дареги сыяктуу көрүнүшү мүмкүн https://api.github.com/repositories/93253246/commits?page=2, ал GitHub өзү тарабынан түзүлгөн.
  4. Андан кийин биз бардык кабыл алынган милдеттенмелерди чыгарабыз, алар түгөнүп калганда, кийинки итерация иштейт while(url), ал дагы бир суроону берет.

Колдонуу мисалы (консолдо авторлорду аткарууну көрсөтөт):

 
 
@A@(async () => {

  let count = 0;

  for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {

    console.log(commit.author.login);

    if (++count == 100) { // остановимся на 100 коммитах
      break;
    }
  }

})();@A@

Бул так биз каалаган нерсе. Беттин суроо-талаптарынын ички механикасы сырттан көрүнбөйт. Биз үчүн бул жөн гана тапшырмаларды кайтаруучу асинхрондук генератор.

Бардыгы

Кадимки итераторлор жана генераторлор түзүү же алуу үчүн убакытты талап кылбаган маалыматтар менен сонун иштешет.

Берилиштер асинхрондук түрдө, кечигүүлөр менен келет деп күткөндө, for await..ofанын ордуна алардын асинхрондук кесиптештерин колдоно алабыз for..of.

Асинхрондук жана кадимки итераторлордун синтаксисиндеги айырмачылыктар:

  Кайталануучу объект Асинхрондук түрдө кайталануучу
Итераторду алуу ыкмасы Symbol.iterator Symbol.asyncIterator
next()кайтып келет {value:…, done: true/false} менен аяктаган убада{value:…, done: true/false}

Асинхрондук жана кадимки генераторлордун синтаксисиндеги айырмачылыктар:

  Генераторлор Асинхрондук генераторлор
Жарыя function* async function*
generator.next()кайтып келет {value:…, done: true/false} менен аяктаган убада{value:…, done: true/false}

Вебди иштеп чыгууда биз көп учурда маалымат агымын алар бөлүктөргө бөлүнгөндө жолуктурабыз. Мисалы, чоң файлды жүктөө же жүктөө.

Мындай маалыматтарды иштетүү үчүн асинхрондук генераторлорду колдоно алабыз. Ошондой эле кээ бир чөйрөлөрдө, мисалы, браузерлерде, мындай маалымат агымдары менен иштөө, аларды трансформациялоо жана бир агымдан экинчи агымга өткөрүү (мисалы, бир булактан жүктөө) үчүн атайын интерфейстерди камсыз кылган Streams (агымдар) деп аталган дагы бир API бар экенин эске алыңыз. жана ошол замат башка жакка жөнөтүлөт).