1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
|
---
title: Making asynchronous programming easier with async and await
slug: Learn/JavaScript/Asynchronous/Async_await
translation_of: Learn/JavaScript/Asynchronous/Async_await
---
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</div>
<p class="summary">В ECMAScript версии 2017 появились <a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async functions</a> и ключевое слово <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a></code> (<a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next support in Mozilla</a>). По существу, такие функции есть синтаксический сахар над Promises и Generator functions (<a href="https://tc39.es/ecmascript-asyncawait/">ts39</a>). С их помощью легче писать/читать асинхронный код, ведь они позволяют использовать привычный синхронный стиль написания. В этой статье мы на базовом уровне разберемся в их устройстве.</p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">Примечания:</th>
<td>Чтобы лучше понять материал, желательно перед чтением ознакомиться с основами JavaScript, асинхронными операциями вообще и объектами Promises.</td>
</tr>
<tr>
<th scope="row">Цель материала:</th>
<td>Научить писать современный асинхронный код с использованием Promises и async functions.</td>
</tr>
</tbody>
</table>
<h2 id="Основы_asyncawait">Основы async/await</h2>
<h3 id="Ключевое_слово_async">Ключевое слово async</h3>
<p>Ключевое слово async позволяет сделать из обычной функции (function declaration или function expression) асинхронную функцию (<a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async function</a>). Такая функция делает две вещи:<br>
- Оборачивает возвращаемое значение в Promise<br>
- Позволяет использовать ключевое слово await (см. дальше)</p>
<p>Попробуйте выполнить в консоли браузера следующий код:</p>
<pre class="brush: js notranslate">function hello() { return "Hello" };
hello();</pre>
<p>Функция возвращает "Hello" — ничего необычного, верно ?</p>
<p>Но что если мы сделаем ее асинхронной ? Проверим:</p>
<pre class="brush: js notranslate">async function hello() { return "Hello" };
hello();</pre>
<p>Как было сказано ранее, вызов асинхронной функции возвращает объект Promise.</p>
<p>Вот пример с <a href="/en-US/docs/Web/JavaScript/Reference/Operators/async_function">async function expression</a>:</p>
<pre class="brush: js notranslate">let hello = async function() { return "Hello" };
hello();</pre>
<p>Также можно использовать стрелочные функции:</p>
<pre class="brush: js notranslate">let hello = async () => { return "Hello" };</pre>
<p>Все они в общем случае делают одно и то же.</p>
<p>Чтобы получить значение, которое возвращает Promise, мы как обычно можем использовать метод <code>.then()</code>:</p>
<pre class="brush: js notranslate">hello().then((value) => console.log(value))</pre>
<p>или еще короче</p>
<pre class="brush: js notranslate">hello().then(console.log)
</pre>
<p>Итак, ключевое слово <code>async</code>, превращает обычную функцию в асинхронную и результат вызова функции оборачивает в Promise. Также асинхронная функция позволяет использовать в своем теле ключевое слово await, о котором далее.</p>
<h3 id="Ключевое_слово_await">Ключевое слово await</h3>
<p>Асинхронные функции становятся по настоящему мощными, когда вы используете ключевое слово <a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a> — по факту, <strong><code>await</code> работает только в асинхронных функциях</strong>. Мы можем использовать await перед promise-based функцией, чтобы остановить поток выполнения и дождаться результата ее выполнения (результат Promise). В то же время, остальной код нашего приложения не блокируется и продолжает работать.</p>
<p>Вы можете использовать <code>await</code> перед любой функцией, что возвращает Promise, включая Browser API функции.</p>
<p>Небольшой пример:</p>
<pre class="brush: js notranslate">async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);</pre>
<p>Конечно, на практике код выше бесполезен, но в учебных целях он иллюстрирует синтаксис асинхронных функций. Теперь давайте перейдем к реальным примерам.</p>
<h2 id="Переписываем_Promises_с_ипользованием_asyncawait">Переписываем Promises с ипользованием async/await</h2>
<p>Давайте посмотрим на пример из предыдущей статьи:</p>
<pre class="brush: js notranslate">fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});</pre>
<p>К этому моменту вы должны понимать как работают Promises, чтобы понять все остальное. Давайте перепишем код используя async/await и оценим разницу.</p>
<pre class="brush: js notranslate">async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});</pre>
<p>Согласитесь, что код стал короче и понятнее — больше никаких блоков <code>.then()</code> по всему скрипту!</p>
<p>Так как ключевое слово <code>async</code> заставляет функцию вернуть Promise, мы можем использовать гибридный подход:</p>
<pre class="brush: js notranslate">async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));</pre>
<p>Можете попрактиковаться самостоятельно, или запустить наш <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await.html">live example</a> (а также <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await.html">source code</a>).</p>
<h3 id="Минуточку_а_как_это_все_работает">Минуточку, а как это все работает ?</h3>
<p>Вы могли заметить, что мы обернули наш код в функцию и сделали ее асинхронной с помощью acync. Это было обязательно - нам надо создать контейнер, внутри которого будет запускаться асинхронный код и будет возмоность дождаться его результата с помощью await, не блокируя остальной код нашег скрипта.</p>
<p>Внутри <code>myFetch()</code> находится код, который слегка напоминает версию на Promise, но есть важные отличия. Вместо того, чтобы писать цепочку блоков <code>.then()</code> мы просто использует ключевое слово <code>await</code> перед вызовом promise-based функции и присваиваем результат в переменную. Ключеовое слово <code>await</code> говорит JavaScript runtime приостановить код в этой строке, не блокируя остальной код скприта за пределами асинхронной функции. Когда вызов promise-based функции будет готов вернуть результат, выполнение продолжится с этой строки дальше.<br>
<br>
Пример:</p>
<pre class="brush: js notranslate">let response = await fetch('coffee.jpg');</pre>
<p>Значение Promise, которое вернет <code>fetch()</code> будет присвоено переменной <code>response</code> только тогда, когда оно будет доступно - парсер делает паузу на данной строке дожидаясь этого момента. Как только значение доступно, парсер переходит к следующей строке, в которой создается объект <code><a href="/en-US/docs/Web/API/Blob">Blob</a></code> из результата Promise. В этой строке, кстати, также используется <code>await</code>, потому что метод <code>.blob()</code> также возвращет Promise. Когда результат готов, мы возвращаем его наружу из <code>myFetch()</code>.</p>
<p>Обратите внимание, когда мы вызываем <code>myFetch()</code>, она возвращает Promise, поэтому мы можем вызвать <code>.then()</code> на результате, чтобы отобразить его на экране.<br>
<br>
К этому моменту вы наверное думаете "Это реально круто!", и вы правы - чем меньше блоков <code>.then()</code>, тем легче читать код.</p>
<h3 id="Добавляем_обработку_ошибок">Добавляем обработку ошибок</h3>
<p><br>
Чтобы обработать ошибки у нас есть несколько вариантов</p>
<p>Мы можем использовать синхронную <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code> структуру с <code>async</code>/<code>await</code>. Вот измененная версия первого примера выше:</p>
<pre class="brush: js notranslate">async function myFetch() {
try {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
} catch(e) {
console.log(e);
}
}
myFetch();</pre>
<p>В блок <code>catch() {}</code> передается объект ошибки, который мы назвали <code>e</code>; мы можем вывести его в консоль, чтобы посмотреть детали: где и почему возникла ошибка.</p>
<p>Если вы хотите использовать гибридный подходы (пример выше), лучше использовать блок <code>.catch()</code> после блока <code>.then()</code> вот так:</p>
<pre class="brush: js notranslate">async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch((e) =>
console.log(e)
);</pre>
<p>Так лучше, потому что блок <code>.catch()</code> словит ошибки как из асинхронной функции, так и из Promise. Если бы мы использовали блок <code>try</code>/<code>catch</code>, мы бы не словили ошибку, которая произошла в самой <code>myFetch()</code> функции.</p>
<p>Вы можете посмотреть оба примера на GitHub:</p>
<ul>
<li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">simple-fetch-async-await-try-catch.html</a> (смотреть <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">source code</a>)</li>
<li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">simple-fetch-async-await-promise-catch.html</a> (смотреть <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">source code</a>)</li>
</ul>
<h2 id="Await_и_Promise.all">Await и Promise.all()</h2>
<p>Как вы помните, асинхронные функции построены поверх <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a>, поэтому они совместимы со всеми возможностями последних. Мы легко можем подождать выполнение <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all()</a></code>, присвоить результат в переменную и все это сделать используя синхронный стиль. Опять, вернемся к <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">an example we saw in our previous article</a>. Откройте пример в соседней вкладке, чтобы лучше понять разницу.</p>
<p>Версия с async/await (смотрите <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-all-async-await.html">live demo</a> и <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-all-async-await.html">source code</a>), сейчас выглядит так:</p>
<pre class="brush: js notranslate">async function fetchAndDecode(url, type) {
let response = await fetch(url);
let content;
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
content = await response.blob();
} else if(type === 'text') {
content = await response.text();
}
return content;
}
}
async function displayContent() {
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');
let values = await Promise.all([coffee, tea, description]);
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
}
displayContent()
.catch((e) =>
console.log(e)
);</pre>
<p>Вы видите, что мы легко изменили <code>fetchAndDecode()</code> функцию в асинхронный вариант. Взгляните на строку с <code>Promise.all()</code>:</p>
<pre class="brush: js notranslate">let values = await Promise.all([coffee, tea, description]);</pre>
<p>С помощью <code>await</code> мы ждем массив результатов всех трех Promises и присваиваем его в переменную <code>values</code>. Это асинхронный код, но он написан в синхронном стиле, за счет чего он гораздо читабельнее.<br>
<br>
Мы должны обернуть весь код в синхронную функцию, <code>displayContent()</code>, и мы не сильно сэкономили на количестве кода, но мы извлекли код блока <code>.then()</code>, за счет чего наш код стал гораздо чище.</p>
<p>Для обработки ошибок мы добавили блок <code>.catch()</code> для функции <code>displayContent()</code>; Это позволило нам отловить ошибки в обоих функциях.</p>
<div class="blockIndicator note">
<p><strong>Заметка</strong>: Мы также можем использовать синхронный блок <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#The_finally_clause">finally</a></code> внутри асинхронной функции, вместо асинхронного <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#The_finally_clause">.finally</a>()</code>, чтобы получить информацию о результате нашей операции — смотрите в действии в нашем <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-finally-async-await.html">live example</a> (смотрите <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-finally-async-await.html">source code</a>).</p>
</div>
<h2 id="Недостатки_asyncawait">Недостатки async/await</h2>
<p>Асинхронные функции с async/await бывают очень удобными, но есть несколько замечаний, о которых полезно знать.</p>
<p>Async/await позволяет вам писать код в синхронном стиле. Ключевое слово <code>await</code> блокирует приостанавливает выполнение ptomise-based функции до того момента, пока promise примет статуc fulfilled. Это не блокирует код за пределами вашей асинхронной функции, тем не менее важно помнить, что внутри асинхронной функции поток выполнения блокируется.<br>
<br>
Ваш код может стать медленнее за счет большого количества awaited promises, которые идут один за другим. Каждый <code>await</code> должен дождаться выполнения предыдущего, тогда как на самом деле мы хотим, чтобы наши Promises выполнялись одновременно, как если бы мы не использовали async/await.<br>
<br>
Есть подход, который позволяет обойти эту проблему - сохранить все выполняющиеся Promises в переменные, а уже после этого дожидаться (awaiting) их результата. Давайте посмотрим на несколько примеров.</p>
<p>We've got two examples available — <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/slow-async-await.html">slow-async-await.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/slow-async-await.html">source code</a>) and <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/fast-async-await.html">fast-async-await.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/fast-async-await.html">source code</a>). Both of them start off with a custom promise function that fakes an async process with a <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code> call:</p>
<pre class="brush: js notranslate">function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};</pre>
<p>Then each one includes a <code>timeTest()</code> async function that awaits three <code>timeoutPromise()</code> calls:</p>
<pre class="brush: js notranslate">async function timeTest() {
...
}</pre>
<p>Each one ends by recording a start time, seeing how long the <code>timeTest()</code> promise takes to fulfill, then recording an end time and reporting how long the operation took in total:</p>
<pre class="brush: js notranslate">let startTime = Date.now();
timeTest().then(() => {
let finishTime = Date.now();
let timeTaken = finishTime - startTime;
alert("Time taken in milliseconds: " + timeTaken);
})</pre>
<p>It is the <code>timeTest()</code> function that differs in each case.</p>
<p>In the <code>slow-async-await.html</code> example, <code>timeTest()</code> looks like this:</p>
<pre class="brush: js notranslate">async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}</pre>
<p>Here we simply await all three <code>timeoutPromise()</code> calls directly, making each one alert for 3 seconds. Each subsequent one is forced to wait until the last one finished — if you run the first example, you'll see the alert box reporting a total run time of around 9 seconds.</p>
<p>In the <code>fast-async-await.html</code> example, <code>timeTest()</code> looks like this:</p>
<pre class="brush: js notranslate">async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}</pre>
<p>Here we store the three <code>Promise</code> objects in variables, which has the effect of setting off their associated processes all running simultaneously.</p>
<p>Next, we await their results — because the promises all started processing at essentially the same time, the promises will all fulfill at the same time; when you run the second example, you'll see the alert box reporting a total run time of just over 3 seconds!</p>
<p>You'll have to test your code carefully, and bear this in mind if performance starts to suffer.</p>
<p>Another minor inconvenience is that you have to wrap your awaited promises inside an async function.</p>
<h2 id="Asyncawait_class_methods">Async/await class methods</h2>
<p>As a final note before we move on, you can even add <code>async</code> in front of class/object methods to make them return promises, and <code>await</code> promises inside them. Take a look at the <a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance#ECMAScript_2015_Classes">ES class code we saw in our object-oriented JavaScript article</a>, and then look at our modified version with an <code>async</code> method:</p>
<pre class="brush: js notranslate">class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
async greeting() {
return await Promise.resolve(`Hi! I'm ${this.name.first}`);
};
farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
};
}
let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);</pre>
<p>The first class method could now be used something like this:</p>
<pre class="brush: js notranslate">han.greeting().then(console.log);</pre>
<h2 id="Browser_support">Browser support</h2>
<p>One consideration when deciding whether to use async/await is support for older browsers. They are available in modern versions of most browsers, the same as promises; the main support problems come with Internet Explorer and Opera Mini.</p>
<p>If you want to use async/await but are concerned about older browser support, you could consider using the <a href="https://babeljs.io/">BabelJS</a> library — this allows you to write your applications using the latest JavaScript and let Babel figure out what changes if any are needed for your user’s browsers. On encountering a browser that does not support async/await, Babel's polyfill can automatically provide fallbacks that work in older browsers.</p>
<h2 id="Conclusion">Conclusion</h2>
<p>And there you have it — async/await provide a nice, simplified way to write async code that is simpler to read and maintain. Even with browser support being more limited than other async code mechanisms at the time of writing, it is well worth learning and considering for use, both for now and in the future.</p>
<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</p>
<h2 id="In_this_module">In this module</h2>
<ul>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">Making asynchronous programming easier with async and await</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li>
</ul>
|