Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ let range = {
};

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

Expand Down Expand Up @@ -105,8 +105,8 @@ let range = {

1. Чтобы сделать объект асинхронно итерируемым, он должен иметь метод `Symbol.asyncIterator` `(1)`.
2. Этот метод должен возвращать объект с методом `next()`, который в свою очередь возвращает промис `(2)`.
3. Метод `next()` не обязательно должен быть `async`, он может быть обычным методом, возвращающим промис, но `async` позволяет использовать `await`, так что это удобно. Здесь мы просто создаем паузу на одну секунду `(3)`.
4. Для итерации мы используем `for await(let value of range)` `(4)`, а именно добавляем "await" после "for". Он вызовет `range[Symbol.asyncIterator]()` один раз, а затем его метод `next()` для получения значений.
3. Метод `next()` не обязательно должен быть `async`, он может быть обычным методом, возвращающим промис, но `async` позволяет использовать `await`, так что это удобно. Здесь мы просто делаем паузу на одну секунду `(3)`.
4. Для итерации мы используем `for await (let value of range)` `(4)`, добавляя "await" после "for". Он вызовет `range[Symbol.asyncIterator]()` один раз, а затем его метод `next()` для получения значений.

Вот небольшая шпаргалка:

Expand Down Expand Up @@ -150,7 +150,7 @@ for(let value of generateSequence(1, 5)) {

Но что если нам нужно использовать `await` в теле генератора? Для выполнения сетевых запросов, например.

Нет проблем, просто добавьте в начале `async`, например вот так:
Нет проблем, просто добавьте в начале `async`, например, вот так:

```js run
*!*async*/!* function* generateSequence(start, end) {
Expand All @@ -171,7 +171,7 @@ for(let value of generateSequence(1, 5)) {

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

})();
Expand Down Expand Up @@ -262,14 +262,14 @@ let range = {

## Пример из реальной практики

До сих пор мы видели простые примеры, чтобы просто получить базовое представление. Теперь давайте рассмотрим реальный пример использования.
До сих пор мы видели простые примеры, чтобы просто получить базовое представление. Теперь давайте рассмотрим реальную ситуацию.

Есть много онлайн-сервисов, которые предоставляют данные постранично. Например, когда нам нужен список пользователей, запрос возвращает предопределенное количество (например, 100) пользователей - "одну страницу", и URL для следующей страницы.
Есть много онлайн-сервисов, которые предоставляют данные постранично. Например, когда нам нужен список пользователей, запрос возвращает предопределенное количество (например, 100) пользователей - "одну страницу", и URL следующей страницы.

Этот подход очень распространен, и речь не только о пользователях, а о чем угодно. Например, GitHub позволяет получать коммиты таким образом, с разбивкой по страницам:
Этот подход очень распространён, и речь не только о пользователях, а о чём угодно. Например, GitHub позволяет получать коммиты таким образом, с разбивкой по страницам:

- Нужно сделать запрос на URL в виде `https://api.github.com/repos/<repo>/commits`.
- В ответ придет JSON с 30 коммитами, а также со ссылкой на следующую страницу в заголовке `Link`.
- В ответ придёт JSON с 30 коммитами, а также со ссылкой на следующую страницу в заголовке `Link`.
- Затем можно использовать эту ссылку для следующего запроса, чтобы получить дополнительную порцию коммитов, и так далее.

Но нам бы, конечно же, хотелось вместо этого сложного взаимодействия иметь просто объект с коммитами, которые можно перебирать, вот так:
Expand All @@ -282,7 +282,7 @@ for await (let commit of fetchCommits(repo)) {
}
```

Мы бы хотели, чтобы вызов, например, `fetchCommits(repo)` получал для нас коммиты, делая запросы всякий раз, когда это необходимо. И пусть он сам разбирается со всем, что касается нумерации страниц, для нас это будет просто `for await..of`.
Мы бы хотели, чтобы вызов `fetchCommits(repo)` получал коммиты, делая запросы всякий раз, когда это необходимо. И пусть он сам разбирается со всем, что касается нумерации страниц, для нас это будет просто `for await..of`.

С асинхронными генераторами это довольно легко реализовать:

Expand Down Expand Up @@ -310,10 +310,10 @@ async function* fetchCommits(repo) {
}
```

1. Мы используем метод [fetch](info:fetch) браузера для загрузки с удаленного URL. Он позволяет при необходимости добавлять авторизацию и другие заголовки, здесь GitHub требует `User-Agent`.
1. Мы используем метод [fetch](info:fetch) браузера для загрузки с удалённого URL. Он позволяет при необходимости добавлять авторизацию и другие заголовки, здесь GitHub требует `User-Agent`.
2. Результат `fetch` обрабатывается как JSON, это опять-таки метод, присущий `fetch`.
3. Нужно получить URL следующей страницы из заголовка ответа `Link`. Он имеет специальный формат, поэтому для этого мы используем регулярное выражение. URL следующей страницы может выглядеть как `https://api.github.com/repositories/93253246/commits?page=2`, он генерируется самим GitHub.
4. Затем мы выдаем все полученные коммиты, а когда они закончатся - сработает следующая итерация `while(url)`, которая сделает еще один запрос.
3. Нужно получить URL следующей страницы из заголовка ответа `Link`. Он имеет специальный формат, поэтому мы используем регулярное выражение. URL следующей страницы может выглядеть как `https://api.github.com/repositories/93253246/commits?page=2`, он генерируется самим GitHub.
4. Затем мы выдаём все полученные коммиты, а когда они закончатся - сработает следующая итерация `while(url)`, которая сделает ещё один запрос.

Пример использования (показывает авторов коммитов в консоли):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

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

const body = await response.json(); // parses response as JSON (array of commits)
const body = await response.json(); // ответ в формате JSON (массив коммитов)

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

url = nextPage;

// yield commits one by one, when they finish - fetch a new page url
// вернуть коммиты один за другим, до окончания страницы
for(let commit of body) {
yield commit;
}
Expand Down