-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy path11_async.html
More file actions
640 lines (433 loc) · 86.5 KB
/
11_async.html
File metadata and controls
640 lines (433 loc) · 86.5 KB
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
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Programação Assíncrona :: JavaScript Eloquente</title>
<link rel=stylesheet href="css/ejs.css"><script>
var page = {"type":"chapter","number":11,"load_files":["code/hangar2.js","code/chapter/11_async.js"]}</script></head>
<article>
<nav><a href="10_modules.html" title="capítulo anterior" aria-label="capítulo anterior">◂</a> <a href="index.html" title="capa" aria-label="capa">●</a> <a href="12_language.html" title="próximo capítulo" aria-label="próximo capítulo">▸</a> <button class=help title="ajuda" aria-label="ajuda"><strong>?</strong></button>
</nav>
<h1>Programação Assíncrona</h1>
<blockquote>
<p><a class="p_ident" id="p-simPLkmmcE" href="#p-simPLkmmcE" tabindex="-1" role="presentation"></a>Quem consegue esperar quieto enquanto a lama assenta?<br>Quem consegue permanecer imóvel até o momento da ação?</p>
<footer>Laozi, <cite>Tao Te Ching</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_11.jpg" alt="Illustration showing two crows on a tree branch"></figure>
<p><a class="p_ident" id="p-J1orhR+Yl2" href="#p-J1orhR+Yl2" tabindex="-1" role="presentation"></a>A parte central de um computador, a parte que executa os passos individuais que compõem nossos programas, é chamada de <em>processador</em>. Os programas que vimos até agora manterão o processador ocupado até terminarem seu trabalho. A velocidade com que algo como um <em>loop</em> que manipula números pode ser executado depende quase inteiramente da velocidade do processador e da memória do computador.</p>
<p><a class="p_ident" id="p-6i9Vnp9sU3" href="#p-6i9Vnp9sU3" tabindex="-1" role="presentation"></a>Mas muitos programas interagem com coisas fora do processador. Por exemplo, podem se comunicar por uma rede de computadores ou solicitar dados do disco rígido — que é muito mais lento do que obtê-los da memória.</p>
<p><a class="p_ident" id="p-7ak3kn0lyt" href="#p-7ak3kn0lyt" tabindex="-1" role="presentation"></a>Quando tal coisa está acontecendo, seria uma pena deixar o processador ocioso — pode haver outro trabalho que ele poderia fazer enquanto isso. Em parte, isso é tratado pelo seu sistema operacional, que alternará o processador entre múltiplos programas em execução. Mas isso não ajuda quando queremos que um <em>único</em> programa consiga progredir enquanto espera por uma requisição de rede.</p>
<h2><a class="h_ident" id="h-b5oergUj1V" href="#h-b5oergUj1V" tabindex="-1" role="presentation"></a>Assincronicidade</h2>
<p><a class="p_ident" id="p-uifvfkEWUm" href="#p-uifvfkEWUm" tabindex="-1" role="presentation"></a>Em um modelo de programação <em>síncrona</em>, as coisas acontecem uma de cada vez. Quando você chama uma função que realiza uma ação de longa duração, ela retorna somente quando a ação terminou e pode retornar o resultado. Isso paralisa seu programa pelo tempo que a ação leva.</p>
<p><a class="p_ident" id="p-Bz6058WdhP" href="#p-Bz6058WdhP" tabindex="-1" role="presentation"></a>Um modelo <em>assíncrono</em> permite que múltiplas coisas aconteçam ao mesmo tempo. Quando você inicia uma ação, seu programa continua executando. Quando a ação termina, o programa é informado e obtém acesso ao resultado (por exemplo, os dados lidos do disco).</p>
<p><a class="p_ident" id="p-iAeoIFbRx9" href="#p-iAeoIFbRx9" tabindex="-1" role="presentation"></a>Podemos comparar a programação síncrona e assíncrona usando um pequeno exemplo: um programa que faz duas requisições pela rede e depois combina os resultados.</p>
<p><a class="p_ident" id="p-9+9ZzItry3" href="#p-9+9ZzItry3" tabindex="-1" role="presentation"></a>Em um ambiente síncrono, onde a função de requisição retorna somente depois de ter feito seu trabalho, a maneira mais fácil de realizar essa tarefa é fazer as requisições uma após a outra. Isso tem a desvantagem de que a segunda requisição só será iniciada quando a primeira terminar. O tempo total levado será no mínimo a soma dos dois tempos de resposta.</p>
<p><a class="p_ident" id="p-GtkD7TCH3f" href="#p-GtkD7TCH3f" tabindex="-1" role="presentation"></a>A solução para esse problema, em um sistema síncrono, é iniciar threads adicionais de controle. Uma <em>thread</em> é outro programa em execução cuja execução pode ser intercalada com outros programas pelo sistema operacional — já que a maioria dos computadores modernos contém múltiplos processadores, múltiplas threads podem até executar ao mesmo tempo, em processadores diferentes. Uma segunda thread poderia iniciar a segunda requisição, e então ambas as threads esperam seus resultados voltarem, após o que se ressincronizam para combinar seus resultados.</p>
<p><a class="p_ident" id="p-QlfDqoHzqD" href="#p-QlfDqoHzqD" tabindex="-1" role="presentation"></a>No diagrama a seguir, as linhas grossas representam o tempo que o programa gasta executando normalmente, e as linhas finas representam o tempo gasto esperando pela rede. No modelo síncrono, o tempo gasto pela rede é <em>parte</em> da linha do tempo para uma dada thread de controle. No modelo assíncrono, iniciar uma ação de rede permite que o programa continue executando enquanto a comunicação de rede acontece ao lado dele, notificando o programa quando terminar.</p><figure><img src="img/control-io.svg" alt="Diagram of showing control flow in synchronous and asynchronous programs. The first part shows a synchronous program, where the program's active and waiting phases all happen on a single, sequential line. The second part shows a multi-threaded synchronous program, with two parallel lines, on which the waiting parts happen alongside each other, causing the program to finish faster. The last part shows an asynchronous program, where the multiple asynchronous actions branch off from the main program, which at some point stops, and then resumes whenever the first thing it was waiting for finishes."></figure>
<p><a class="p_ident" id="p-fbENL0yxl3" href="#p-fbENL0yxl3" tabindex="-1" role="presentation"></a>Outra maneira de descrever a diferença é que esperar por ações terminarem é <em>implícito</em> no modelo síncrono, enquanto é <em>explícito</em> — sob nosso controle — no assíncrono.</p>
<p><a class="p_ident" id="p-rvozsj9Ima" href="#p-rvozsj9Ima" tabindex="-1" role="presentation"></a>A assincronicidade corta para os dois lados. Ela facilita expressar programas que não se encaixam no modelo linear de controle, mas também pode tornar mais estranho expressar programas que seguem uma linha reta. Veremos algumas maneiras de reduzir essa estranheza mais adiante no capítulo.</p>
<p><a class="p_ident" id="p-GTU+GLZca1" href="#p-GTU+GLZca1" tabindex="-1" role="presentation"></a>Ambas as plataformas proeminentes de programação JavaScript — browsers e Node.js — tornam assíncronas operações que podem levar algum tempo, em vez de depender de threads. Como programar com threads é notoriamente difícil (entender o que um programa faz é muito mais difícil quando ele faz múltiplas coisas ao mesmo tempo), isso é geralmente considerado algo bom.</p>
<h2><a class="h_ident" id="h-n9ws/jdPpb" href="#h-n9ws/jdPpb" tabindex="-1" role="presentation"></a>Callbacks</h2>
<p><a class="p_ident" id="p-Vx/zKnp+FT" href="#p-Vx/zKnp+FT" tabindex="-1" role="presentation"></a>Uma abordagem à programação assíncrona é fazer com que funções que precisam esperar por algo recebam um argumento extra, uma <em>função de callback</em>. A função assíncrona inicia um processo, configura as coisas para que a função de <em>callback</em> seja chamada quando o processo terminar e então retorna.</p>
<p><a class="p_ident" id="p-gMRtzWWEOF" href="#p-gMRtzWWEOF" tabindex="-1" role="presentation"></a>Como exemplo, a função <code>setTimeout</code>, disponível tanto no Node.js quanto em <em>browsers</em>, espera um dado número de milissegundos e então chama uma função.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RyFm7Uoiuv" href="#c-RyFm7Uoiuv" tabindex="-1" role="presentation"></a>setTimeout(() => console.log(<span class="tok-string">"Tick"</span>), <span class="tok-number">500</span>);</pre>
<p><a class="p_ident" id="p-Ac1H6K8ZR0" href="#p-Ac1H6K8ZR0" tabindex="-1" role="presentation"></a>Esperar geralmente não é um trabalho importante, mas pode ser muito útil quando você precisa organizar algo para acontecer em um certo momento ou verificar se alguma ação está levando mais tempo do que o esperado.</p>
<p><a class="p_ident" id="p-4vOK52Xye9" href="#p-4vOK52Xye9" tabindex="-1" role="presentation"></a>Outro exemplo de uma operação assíncrona comum é ler um arquivo do armazenamento de um dispositivo. Imagine que você tem uma função <code>readTextFile</code> que lê o conteúdo de um arquivo como <em>string</em> e o passa para uma função de <em>callback</em>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6de9nmxjo2" href="#c-6de9nmxjo2" tabindex="-1" role="presentation"></a>readTextFile(<span class="tok-string">"shopping_list.txt"</span>, <span class="tok-definition">content</span> => {
console.log(<span class="tok-string2">`Shopping List:\n</span>${content}<span class="tok-string2">`</span>);
});
<span class="tok-comment">// → Shopping List:</span>
<span class="tok-comment">// → Peanut butter</span>
<span class="tok-comment">// → Bananas</span></pre>
<p><a class="p_ident" id="p-OEJ3qPA9lN" href="#p-OEJ3qPA9lN" tabindex="-1" role="presentation"></a>A função <code>readTextFile</code> não faz parte do JavaScript padrão. Veremos como ler arquivos no <em>browser</em> e no Node.js em capítulos posteriores.</p>
<p><a class="p_ident" id="p-70uOBZEK1d" href="#p-70uOBZEK1d" tabindex="-1" role="presentation"></a>Realizar múltiplas ações assíncronas em sequência usando <em>callbacks</em> significa que você precisa continuar passando novas funções para lidar com a continuação da computação após as ações. Uma função assíncrona que compara dois arquivos e produz um booleano indicando se o conteúdo deles é o mesmo poderia parecer assim:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-txIvHmdCN1" href="#c-txIvHmdCN1" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">compareFiles</span>(<span class="tok-definition">fileA</span>, <span class="tok-definition">fileB</span>, <span class="tok-definition">callback</span>) {
readTextFile(fileA, <span class="tok-definition">contentA</span> => {
readTextFile(fileB, <span class="tok-definition">contentB</span> => {
callback(contentA == contentB);
});
});
}</pre>
<p><a class="p_ident" id="p-1HHmzd5PJ4" href="#p-1HHmzd5PJ4" tabindex="-1" role="presentation"></a>Esse estilo de programação é viável, mas o nível de indentação aumenta a cada ação assíncrona porque você acaba dentro de outra função. Fazer coisas mais complicadas, como envolver ações assíncronas em um <em>loop</em>, pode ficar estranho.</p>
<p><a class="p_ident" id="p-+pjftmQHqY" href="#p-+pjftmQHqY" tabindex="-1" role="presentation"></a>De certa forma, a assincronicidade é <em>contagiosa</em>. Qualquer função que chama uma função que funciona assincronamente deve ela própria ser assíncrona, usando um <em>callback</em> ou mecanismo semelhante para entregar seu resultado. Chamar um <em>callback</em> é um pouco mais complexo e propenso a erros do que simplesmente retornar um valor, então precisar estruturar grandes partes do seu programa dessa forma não é ótimo.</p>
<h2><a class="h_ident" id="h-sdRy5CTAP/" href="#h-sdRy5CTAP/" tabindex="-1" role="presentation"></a>Promises</h2>
<p><a class="p_ident" id="p-Y2SqD0YteQ" href="#p-Y2SqD0YteQ" tabindex="-1" role="presentation"></a>Uma maneira ligeiramente diferente de construir um programa assíncrono é ter funções assíncronas que retornam um objeto que representa seu resultado (futuro) em vez de passar funções de <em>callback</em>. Dessa forma, tais funções realmente retornam algo significativo, e o formato do programa se assemelha mais ao de programas síncronos.</p>
<p><a class="p_ident" id="p-xEoJJuvsFM" href="#p-xEoJJuvsFM" tabindex="-1" role="presentation"></a>É para isso que serve a classe padrão <code>Promise</code>. Uma <em>promise</em> é um recibo representando um valor que pode não estar disponível ainda. Ela fornece um método <code>then</code> que permite registrar uma função que deve ser chamada quando a ação que ela aguarda terminar. Quando a <em>promise</em> é <em>resolvida</em>, significando que seu valor se torna disponível, tais funções (pode haver múltiplas) são chamadas com o valor do resultado. É possível chamar <code>then</code> em uma <em>promise</em> que já foi resolvida — sua função ainda será chamada.</p>
<p><a class="p_ident" id="p-5f9OVoOS7u" href="#p-5f9OVoOS7u" tabindex="-1" role="presentation"></a>A maneira mais fácil de criar uma <em>promise</em> é chamando <code>Promise.resolve</code>. Essa função garante que o valor que você dá a ela seja envolvido em uma <em>promise</em>. Se já for uma <em>promise</em>, é simplesmente retornada. Caso contrário, você obtém uma nova <em>promise</em> que resolve imediatamente com seu valor como resultado.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-fzJ7VLwQ/i" href="#c-fzJ7VLwQ/i" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">fifteen</span> = Promise.resolve(<span class="tok-number">15</span>);
fifteen.then(<span class="tok-definition">value</span> => console.log(<span class="tok-string2">`Got </span>${value}<span class="tok-string2">`</span>));
<span class="tok-comment">// → Got 15</span></pre>
<p><a class="p_ident" id="p-zrhvhSfJP/" href="#p-zrhvhSfJP/" tabindex="-1" role="presentation"></a>Para criar uma <em>promise</em> que não resolve imediatamente, você pode usar <code>Promise</code> como construtor. Ele tem uma interface um tanto estranha: o construtor espera uma função como argumento, que ele chama imediatamente, passando a ela uma função que ela pode usar para resolver a <em>promise</em>.</p>
<p><a class="p_ident" id="p-hhqHr2sEVn" href="#p-hhqHr2sEVn" tabindex="-1" role="presentation"></a>Por exemplo, é assim que você poderia criar uma interface baseada em <em>promises</em> para a função <code>readTextFile</code>:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Pag33DRjud" href="#c-Pag33DRjud" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">textFile</span>(<span class="tok-definition">filename</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">resolve</span> => {
readTextFile(filename, <span class="tok-definition">text</span> => resolve(text));
});
}
textFile(<span class="tok-string">"plans.txt"</span>).then(console.log);</pre>
<p><a class="p_ident" id="p-j555nd8Ksw" href="#p-j555nd8Ksw" tabindex="-1" role="presentation"></a>Note como, em contraste com funções no estilo <em>callback</em>, esta função assíncrona retorna um valor significativo — uma <em>promise</em> de dar a você o conteúdo do arquivo em algum momento no futuro.</p>
<p><a class="p_ident" id="p-7YunvV3Ege" href="#p-7YunvV3Ege" tabindex="-1" role="presentation"></a>Uma coisa útil sobre o método <code>then</code> é que ele próprio retorna outra <em>promise</em>. Esta resolve para o valor retornado pela função de <em>callback</em> ou, se o valor retornado for uma <em>promise</em>, para o valor para o qual essa <em>promise</em> resolve. Assim, você pode “encadear” múltiplas chamadas a <code>then</code> para configurar uma sequência de ações assíncronas.</p>
<p><a class="p_ident" id="p-wu97DyUHPB" href="#p-wu97DyUHPB" tabindex="-1" role="presentation"></a>Esta função, que lê um arquivo cheio de nomes de arquivos e retorna o conteúdo de um arquivo aleatório naquela lista, mostra esse tipo de <em>pipeline</em> assíncrono de <em>promises</em>:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-cNJbukKxCb" href="#c-cNJbukKxCb" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">randomFile</span>(<span class="tok-definition">listFile</span>) {
<span class="tok-keyword">return</span> textFile(listFile)
.then(<span class="tok-definition">content</span> => content.trim().split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>))
.then(<span class="tok-definition">ls</span> => ls[Math.floor(Math.random() * ls.length)])
.then(<span class="tok-definition">filename</span> => textFile(filename));
}</pre>
<p><a class="p_ident" id="p-tbGi99UCxH" href="#p-tbGi99UCxH" tabindex="-1" role="presentation"></a>A função retorna o resultado dessa cadeia de chamadas <code>then</code>. A <em>promise</em> inicial busca a lista de arquivos como <em>string</em>. A primeira chamada <code>then</code> transforma essa <em>string</em> em um <em>array</em> de linhas, produzindo uma nova <em>promise</em>. A segunda chamada <code>then</code> escolhe uma linha aleatória, produzindo uma terceira <em>promise</em> que contém um único nome de arquivo. A chamada <code>then</code> final lê esse arquivo, então o resultado da função como um todo é uma <em>promise</em> que retorna o conteúdo de um arquivo aleatório.</p>
<p><a class="p_ident" id="p-DZ8rgtl3Wv" href="#p-DZ8rgtl3Wv" tabindex="-1" role="presentation"></a>Nesse código, as funções usadas nas duas primeiras chamadas <code>then</code> retornam um valor regular que será imediatamente passado para a <em>promise</em> retornada por <code>then</code> quando a função retornar. A última chamada <code>then</code> retorna uma <em>promise</em> (<code>textFile(filename)</code>), tornando-a um passo assíncrono real.</p>
<p><a class="p_ident" id="p-ibPSucLJJp" href="#p-ibPSucLJJp" tabindex="-1" role="presentation"></a>Também teria sido possível realizar todos esses passos dentro de um único <em>callback</em> <code>then</code>, pois apenas o último passo é realmente assíncrono. Mas o tipo de wrappers <code>then</code> que apenas fazem alguma transformação síncrona de dados é frequentemente útil, como quando se quer retornar uma <em>promise</em> que produz uma versão processada de algum resultado assíncrono.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6arBJJ0ZQF" href="#c-6arBJJ0ZQF" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">jsonFile</span>(<span class="tok-definition">filename</span>) {
<span class="tok-keyword">return</span> textFile(filename).then(JSON.parse);
}
jsonFile(<span class="tok-string">"package.json"</span>).then(console.log);</pre>
<p><a class="p_ident" id="p-4IZiXgD3ck" href="#p-4IZiXgD3ck" tabindex="-1" role="presentation"></a>Geralmente, é útil pensar em uma <em>promise</em> como um dispositivo que permite ao código ignorar a questão de quando um valor vai chegar. Um valor normal precisa realmente existir antes de podermos referenciá-lo. Um valor prometido é um valor que <em>pode</em> já estar lá ou pode aparecer em algum ponto no futuro. Computações definidas em termos de <em>promises</em>, conectando-as com chamadas <code>then</code>, são executadas assincronamente conforme suas entradas se tornam disponíveis.</p>
<h2><a class="h_ident" id="h-oYPteQ6WrO" href="#h-oYPteQ6WrO" tabindex="-1" role="presentation"></a>Falha</h2>
<p><a class="p_ident" id="p-ihqB6r+nLJ" href="#p-ihqB6r+nLJ" tabindex="-1" role="presentation"></a>Computações JavaScript regulares podem falhar lançando uma exceção. Computações assíncronas frequentemente precisam de algo assim. Uma requisição de rede pode falhar, um arquivo pode não existir, ou algum código que faz parte da computação assíncrona pode lançar uma exceção.</p>
<p><a class="p_ident" id="p-Gbx+eHQjGq" href="#p-Gbx+eHQjGq" tabindex="-1" role="presentation"></a>Um dos problemas mais urgentes com o estilo <em>callback</em> de programação assíncrona é que torna extremamente difícil garantir que falhas sejam adequadamente reportadas aos <em>callbacks</em>.</p>
<p><a class="p_ident" id="p-xwgevtZrCc" href="#p-xwgevtZrCc" tabindex="-1" role="presentation"></a>Uma convenção comum é usar o primeiro argumento do <em>callback</em> para indicar que a ação falhou, e o segundo para passar o valor produzido pela ação quando foi bem-sucedida.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yK141KNJqF" href="#c-yK141KNJqF" tabindex="-1" role="presentation"></a>someAsyncFunction((<span class="tok-definition">error</span>, <span class="tok-definition">value</span>) => {
<span class="tok-keyword">if</span> (error) handleError(error);
<span class="tok-keyword">else</span> processValue(value);
});</pre>
<p><a class="p_ident" id="p-QXplMxAnAS" href="#p-QXplMxAnAS" tabindex="-1" role="presentation"></a>Tais funções de <em>callback</em> devem sempre verificar se receberam uma exceção e garantir que quaisquer problemas que causem, incluindo exceções lançadas por funções que chamam, sejam capturados e dados à função correta.</p>
<p><a class="p_ident" id="p-wMX4sY8ZqY" href="#p-wMX4sY8ZqY" tabindex="-1" role="presentation"></a><em>Promises</em> tornam isso mais fácil. Elas podem ser resolvidas (a ação terminou com sucesso) ou rejeitadas (ela falhou). Handlers de resolução (registrados com <code>then</code>) são chamados apenas quando a ação é bem-sucedida, e rejeições são propagadas para a nova <em>promise</em> retornada por <code>then</code>. Quando um handler lança uma exceção, isso automaticamente faz com que a <em>promise</em> produzida por sua chamada <code>then</code> seja rejeitada. Se qualquer elemento em uma cadeia de ações assíncronas falhar, o resultado da cadeia inteira é marcado como rejeitado, e nenhum handler de sucesso é chamado além do ponto onde falhou.</p>
<p><a class="p_ident" id="p-lNM8Gy12FA" href="#p-lNM8Gy12FA" tabindex="-1" role="presentation"></a>Assim como resolver uma <em>promise</em> fornece um valor, rejeitar uma também fornece um valor, geralmente chamado de <em>razão</em> da rejeição. Quando uma exceção em uma função handler causa a rejeição, o valor da exceção é usado como razão. Da mesma forma, quando um handler retorna uma <em>promise</em> que é rejeitada, essa rejeição flui para a próxima <em>promise</em>. Existe uma função <code>Promise.reject</code> que cria uma nova <em>promise</em> imediatamente rejeitada.</p>
<p><a class="p_ident" id="p-eTMw46ACoq" href="#p-eTMw46ACoq" tabindex="-1" role="presentation"></a>Para lidar explicitamente com tais rejeições, <em>promises</em> possuem um método <code>catch</code> que registra um handler para ser chamado quando a <em>promise</em> é rejeitada, semelhante a como handlers <code>then</code> lidam com resolução normal. Também é muito parecido com <code>then</code> no sentido de que retorna uma nova <em>promise</em>, que resolve para o valor da <em>promise</em> original quando resolve normalmente e para o resultado do handler <code>catch</code> caso contrário. Se um handler <code>catch</code> lança um erro, a nova <em>promise</em> também é rejeitada.</p>
<p><a class="p_ident" id="p-cCkUfnBeFn" href="#p-cCkUfnBeFn" tabindex="-1" role="presentation"></a>Como atalho, <code>then</code> também aceita um handler de rejeição como segundo argumento, para que você possa instalar ambos os tipos de handlers em uma única chamada de método: <code>.<wbr>then(acceptHandler, rejectHandler)</code>.</p>
<p><a class="p_ident" id="p-2c1cvlnsWO" href="#p-2c1cvlnsWO" tabindex="-1" role="presentation"></a>Uma função passada ao construtor <code>Promise</code> recebe um segundo argumento, além da função de resolução, que pode usar para rejeitar a nova <em>promise</em>.</p>
<p><a class="p_ident" id="p-+LjnBSNkno" href="#p-+LjnBSNkno" tabindex="-1" role="presentation"></a>Quando nossa função <code>readTextFile</code> encontra um problema, ela passa o erro para sua função de <em>callback</em> como segundo argumento. Nosso wrapper <code>textFile</code> deveria na verdade verificar esse argumento para que uma falha faça a <em>promise</em> que retorna ser rejeitada.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Z3TGL4evDx" href="#c-Z3TGL4evDx" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">textFile</span>(<span class="tok-definition">filename</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => {
readTextFile(filename, (<span class="tok-definition">text</span>, <span class="tok-definition">error</span>) => {
<span class="tok-keyword">if</span> (error) reject(error);
<span class="tok-keyword">else</span> resolve(text);
});
});
}</pre>
<p><a class="p_ident" id="p-pemZZNISXR" href="#p-pemZZNISXR" tabindex="-1" role="presentation"></a>As cadeias de valores de <em>promise</em> criadas por chamadas a <code>then</code> e <code>catch</code> formam assim um <em>pipeline</em> através do qual valores assíncronos ou falhas se movem. Como tais cadeias são criadas registrando handlers, cada elo tem um handler de sucesso ou um handler de rejeição (ou ambos) associado a ele. Handlers que não correspondem ao tipo de resultado (sucesso ou falha) são ignorados. Handlers que correspondem são chamados, e seu resultado determina que tipo de valor vem em seguida — sucesso quando retornam um valor que não é <em>promise</em>, rejeição quando lançam uma exceção, e o resultado da <em>promise</em> quando retornam uma <em>promise</em>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-F/gRrC8zg/" href="#c-F/gRrC8zg/" tabindex="-1" role="presentation"></a><span class="tok-keyword">new</span> Promise((<span class="tok-definition">_</span>, <span class="tok-definition">reject</span>) => reject(<span class="tok-keyword">new</span> Error(<span class="tok-string">"Fail"</span>)))
.then(<span class="tok-definition">value</span> => console.log(<span class="tok-string">"Handler 1:"</span>, value))
.catch(<span class="tok-definition">reason</span> => {
console.log(<span class="tok-string">"Caught failure "</span> + reason);
<span class="tok-keyword">return</span> <span class="tok-string">"nothing"</span>;
})
.then(<span class="tok-definition">value</span> => console.log(<span class="tok-string">"Handler 2:"</span>, value));
<span class="tok-comment">// → Caught failure Error: Fail</span>
<span class="tok-comment">// → Handler 2: nothing</span></pre>
<p><a class="p_ident" id="p-fBT6dzR7O0" href="#p-fBT6dzR7O0" tabindex="-1" role="presentation"></a>A primeira função handler <code>then</code> não é chamada porque nesse ponto do <em>pipeline</em> a <em>promise</em> contém uma rejeição. O handler <code>catch</code> lida com essa rejeição e retorna um valor, que é dado à segunda função handler <code>then</code>.</p>
<p><a class="p_ident" id="p-zCfZYA6SH9" href="#p-zCfZYA6SH9" tabindex="-1" role="presentation"></a>Assim como uma exceção não capturada é tratada pelo ambiente, ambientes JavaScript podem detectar quando uma rejeição de <em>promise</em> não é tratada e reportarão isso como um erro.</p>
<h2><a class="h_ident" id="h-XxJsV0JUaZ" href="#h-XxJsV0JUaZ" tabindex="-1" role="presentation"></a>Carla</h2>
<p><a class="p_ident" id="p-Qky8VXZqFG" href="#p-Qky8VXZqFG" tabindex="-1" role="presentation"></a>É um dia ensolarado em Berlim. A pista do antigo aeroporto desativado está repleta de ciclistas e patinadores. Na grama perto de um contêiner de lixo, um bando de corvos ruidosamente se movimenta, tentando convencer um grupo de turistas a abrir mão de seus sanduíches.</p>
<p><a class="p_ident" id="p-W34NIIS+w3" href="#p-W34NIIS+w3" tabindex="-1" role="presentation"></a>Uma das corvos se destaca — uma fêmea grande e desgrenhada com algumas penas brancas na asa direita. Ela engana as pessoas com uma habilidade e confiança que sugerem que faz isso há muito tempo. Quando um senhor idoso é distraído pelas artimanhas de outra corvo, ela calmamente mergulha, agarra o pãozinho meio-comido da mão dele e voa embora.</p>
<p><a class="p_ident" id="p-6N8Iz2L9O5" href="#p-6N8Iz2L9O5" tabindex="-1" role="presentation"></a>Ao contrário do resto do grupo, que parece feliz em passar o dia fazendo palhaçadas ali, a corvo grande parece determinada. Carregando seu espólio, ela voa direto para o telhado do prédio do hangar, desaparecendo em uma abertura de ventilação.</p>
<p><a class="p_ident" id="p-b2N9EYqTsz" href="#p-b2N9EYqTsz" tabindex="-1" role="presentation"></a>Dentro do prédio, ouve-se um som estranho de batidas — suave, mas persistente. Ele vem de um espaço estreito sob o telhado de uma escadaria inacabada. A corvo está sentada ali, cercada por seus lanches roubados, meia dúzia de smartphones (vários dos quais estão ligados) e uma bagunça de cabos. Ela bate rapidamente na tela de um dos telefones com o bico. Palavras estão aparecendo nele. Se você não soubesse melhor, pensaria que ela está digitando.</p>
<p><a class="p_ident" id="p-4GbXh1YCfz" href="#p-4GbXh1YCfz" tabindex="-1" role="presentation"></a>Essa corvo é conhecida por seus pares como “cāāw-krö". Mas como esses sons são pouco adequados para cordas vocais humanas, vamos nos referir a ela como Carla.</p>
<p><a class="p_ident" id="p-eLQmCaGcsC" href="#p-eLQmCaGcsC" tabindex="-1" role="presentation"></a>Carla é uma corvo um tanto peculiar. Na juventude, era fascinada pela linguagem humana, bisbilhotando as pessoas até ter um bom domínio do que diziam. Mais tarde na vida, seu interesse mudou para a tecnologia humana, e ela começou a roubar telefones para estudá-los. Seu projeto atual é aprender a programar. O texto que está digitando em seu laboratório escondido é, na verdade, um trecho de código JavaScript assíncrono.</p>
<h2><a class="h_ident" id="h-LlMALCXzH8" href="#h-LlMALCXzH8" tabindex="-1" role="presentation"></a>Invadindo</h2>
<p><a class="p_ident" id="p-l4eiuM9Xhg" href="#p-l4eiuM9Xhg" tabindex="-1" role="presentation"></a>Carla adora a internet. Infelizmente, o telefone em que está trabalhando está prestes a ficar sem dados pré-pagos. O prédio tem uma rede sem fio, mas requer um código para acessar.</p>
<p><a class="p_ident" id="p-Ioduvz+YM+" href="#p-Ioduvz+YM+" tabindex="-1" role="presentation"></a>Felizmente, os roteadores sem fio do prédio têm 20 anos e são mal protegidos. Pesquisando um pouco, Carla descobre que o mecanismo de autenticação da rede tem uma falha que ela pode usar. Ao entrar na rede, um dispositivo deve enviar a senha correta de seis dígitos. O ponto de acesso responderá com uma mensagem de sucesso ou falha dependendo de se o código correto foi fornecido. Porém, ao enviar um código parcial (digamos, apenas três dígitos), a resposta é diferente dependendo de se aqueles dígitos são o início correto do código ou não. Enviar números incorretos retorna imediatamente uma mensagem de falha. Ao enviar os corretos, o ponto de acesso espera por mais dígitos.</p>
<p><a class="p_ident" id="p-9U8IX19KJ7" href="#p-9U8IX19KJ7" tabindex="-1" role="presentation"></a>Isso torna possível acelerar enormemente a adivinhação do número. Carla pode encontrar o primeiro dígito tentando cada número por vez, até encontrar um que não retorne falha imediatamente. Tendo um dígito, ela pode encontrar o segundo da mesma forma, e assim por diante, até saber toda a senha.</p>
<p><a class="p_ident" id="p-uf/PUvIbvg" href="#p-uf/PUvIbvg" tabindex="-1" role="presentation"></a>Assuma que Carla tem uma função <code>joinWifi</code>. Dado o nome da rede e a senha (como <em>string</em>), a função tenta entrar na rede, retornando uma <em>promise</em> que resolve se bem-sucedida e rejeita se a autenticação falhou. A primeira coisa que ela precisa é uma maneira de envolver uma <em>promise</em> de modo que ela automaticamente rejeite se levar muito tempo, para permitir que o programa avance rapidamente se o ponto de acesso não responder.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lQa8l7DNj9" href="#c-lQa8l7DNj9" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">withTimeout</span>(<span class="tok-definition">promise</span>, <span class="tok-definition">time</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => {
promise.then(resolve, reject);
setTimeout(() => reject(<span class="tok-string">"Timed out"</span>), time);
});
}</pre>
<p><a class="p_ident" id="p-K6j9ILT6Ca" href="#p-K6j9ILT6Ca" tabindex="-1" role="presentation"></a>Isso usa o fato de que uma <em>promise</em> pode ser resolvida ou rejeitada apenas uma vez. Se a <em>promise</em> dada como argumento resolver ou rejeitar primeiro, esse resultado será o resultado da <em>promise</em> retornada por <code>withTimeout</code>. Se, por outro lado, o <code>setTimeout</code> disparar primeiro, rejeitando a <em>promise</em>, quaisquer chamadas posteriores de resolução ou rejeição são ignoradas.</p>
<p><a class="p_ident" id="p-zHtVE6rnT7" href="#p-zHtVE6rnT7" tabindex="-1" role="presentation"></a>Para encontrar a senha inteira, o programa precisa repetidamente procurar o próximo dígito tentando cada dígito. Se a autenticação tem sucesso, sabemos que encontramos o que procurávamos. Se falha imediatamente, sabemos que aquele dígito estava errado e devemos tentar o próximo. Se a requisição expira, encontramos outro dígito correto e devemos continuar adicionando outro dígito.</p>
<p><a class="p_ident" id="p-f9vHggi4uL" href="#p-f9vHggi4uL" tabindex="-1" role="presentation"></a>Como você não pode esperar por uma <em>promise</em> dentro de um <em>loop</em> <code>for</code>, Carla usa uma função recursiva para conduzir esse processo. A cada chamada, essa função recebe o código como o conhecemos até agora, bem como o próximo dígito a tentar. Dependendo do que acontece, ela pode retornar um código terminado ou chamar a si mesma, para começar a descobrir a próxima posição no código ou para tentar novamente com outro dígito.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rKTBHF8p2l" href="#c-rKTBHF8p2l" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">crackPasscode</span>(<span class="tok-definition">networkID</span>) {
<span class="tok-keyword">function</span> <span class="tok-definition">nextDigit</span>(<span class="tok-definition">code</span>, <span class="tok-definition">digit</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">newCode</span> = code + digit;
<span class="tok-keyword">return</span> withTimeout(joinWifi(networkID, newCode), <span class="tok-number">50</span>)
.then(() => newCode)
.catch(<span class="tok-definition">failure</span> => {
<span class="tok-keyword">if</span> (failure == <span class="tok-string">"Timed out"</span>) {
<span class="tok-keyword">return</span> nextDigit(newCode, <span class="tok-number">0</span>);
} <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (digit < <span class="tok-number">9</span>) {
<span class="tok-keyword">return</span> nextDigit(code, digit + <span class="tok-number">1</span>);
} <span class="tok-keyword">else</span> {
<span class="tok-keyword">throw</span> failure;
}
});
}
<span class="tok-keyword">return</span> nextDigit(<span class="tok-string">""</span>, <span class="tok-number">0</span>);
}</pre>
<p><a class="p_ident" id="p-gg3a4SSAKe" href="#p-gg3a4SSAKe" tabindex="-1" role="presentation"></a>O ponto de acesso tende a responder a requisições de autenticação inválidas em cerca de 20 milissegundos, então para segurança, esta função espera 50 milissegundos antes de expirar uma requisição.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QKY6kJy25c" href="#c-QKY6kJy25c" tabindex="-1" role="presentation"></a>crackPasscode(<span class="tok-string">"HANGAR 2"</span>).then(console.log);
<span class="tok-comment">// → 555555</span></pre>
<p><a class="p_ident" id="p-5YU3o5NrpV" href="#p-5YU3o5NrpV" tabindex="-1" role="presentation"></a>Carla inclina a cabeça e suspira. Isso teria sido mais satisfatório se o código fosse um pouco mais difícil de adivinhar.</p>
<h2><a class="h_ident" id="h-IBzLnlGcih" href="#h-IBzLnlGcih" tabindex="-1" role="presentation"></a>Funções async</h2>
<p><a class="p_ident" id="p-1joxW9/xuB" href="#p-1joxW9/xuB" tabindex="-1" role="presentation"></a>Mesmo com <em>promises</em>, esse tipo de código assíncrono é irritante de escrever. <em>Promises</em> frequentemente precisam ser conectadas de maneiras verbosas e aparentemente arbitrárias. Para criar um <em>loop</em> assíncrono, Carla foi forçada a introduzir uma função recursiva.</p>
<p><a class="p_ident" id="p-l/JRZxiH53" href="#p-l/JRZxiH53" tabindex="-1" role="presentation"></a>O que a função de quebra de senha realmente faz é completamente linear — ela sempre espera que a ação anterior complete antes de iniciar a próxima. Em um modelo de programação síncrona, seria mais direto de expressar.</p>
<p><a class="p_ident" id="p-tKMCLRdf7f" href="#p-tKMCLRdf7f" tabindex="-1" role="presentation"></a>A boa notícia é que o JavaScript permite escrever código pseudossíncrono para descrever computação assíncrona. Uma função <code>async</code> retorna implicitamente uma <em>promise</em> e pode, em seu corpo, usar <code>await</code> em outras <em>promises</em> de uma maneira que <em>parece</em> síncrona.</p>
<p><a class="p_ident" id="p-1b7g1yAknJ" href="#p-1b7g1yAknJ" tabindex="-1" role="presentation"></a>Podemos reescrever <code>crackPasscode</code> assim:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-f8fODDM35B" href="#c-f8fODDM35B" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">crackPasscode</span>(<span class="tok-definition">networkID</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">code</span> = <span class="tok-string">""</span>;;) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">digit</span> = <span class="tok-number">0</span>;; digit++) {
<span class="tok-keyword">let</span> <span class="tok-definition">newCode</span> = code + digit;
<span class="tok-keyword">try</span> {
<span class="tok-keyword">await</span> withTimeout(joinWifi(networkID, newCode), <span class="tok-number">50</span>);
<span class="tok-keyword">return</span> newCode;
} <span class="tok-keyword">catch</span> (<span class="tok-definition">failure</span>) {
<span class="tok-keyword">if</span> (failure == <span class="tok-string">"Timed out"</span>) {
code = newCode;
<span class="tok-keyword">break</span>;
} <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (digit == <span class="tok-number">9</span>) {
<span class="tok-keyword">throw</span> failure;
}
}
}
}
}</pre>
<p><a class="p_ident" id="p-uCRpsk4ez5" href="#p-uCRpsk4ez5" tabindex="-1" role="presentation"></a>Essa versão mostra mais claramente a estrutura de <em>loop</em> duplo da função (o <em>loop</em> interno tenta os dígitos de 0 a 9 e o <em>loop</em> externo adiciona dígitos à senha).</p>
<p><a class="p_ident" id="p-yMiil92ZH7" href="#p-yMiil92ZH7" tabindex="-1" role="presentation"></a>Uma função <code>async</code> é marcada pela palavra <code>async</code> antes da palavra-chave <code>function</code>. Métodos também podem ser tornados <code>async</code> escrevendo <code>async</code> antes de seu nome. Quando tal função ou método é chamado, retorna uma <em>promise</em>. Assim que a função retorna algo, essa <em>promise</em> é resolvida. Se o corpo lança uma exceção, a <em>promise</em> é rejeitada.</p>
<p><a class="p_ident" id="p-pxjFAHCqjN" href="#p-pxjFAHCqjN" tabindex="-1" role="presentation"></a>Dentro de uma função <code>async</code>, a palavra <code>await</code> pode ser colocada na frente de uma expressão para esperar que uma <em>promise</em> resolva e só então continuar a execução da função. Se a <em>promise</em> rejeitar, uma exceção é levantada no ponto do <code>await</code>.</p>
<p><a class="p_ident" id="p-Qr8xXZC+N6" href="#p-Qr8xXZC+N6" tabindex="-1" role="presentation"></a>Tal função não executa mais do início ao fim de uma só vez como uma função JavaScript regular. Em vez disso, pode ser <em>congelada</em> em qualquer ponto que tenha um <code>await</code> e retomada em um momento posterior.</p>
<p><a class="p_ident" id="p-vfCt4csutU" href="#p-vfCt4csutU" tabindex="-1" role="presentation"></a>Para a maioria do código assíncrono, essa notação é mais conveniente do que usar <em>promises</em> diretamente. Você ainda precisa de um entendimento de <em>promises</em>, já que em muitos casos ainda interagirá com elas diretamente. Mas ao conectá-las, funções <code>async</code> são geralmente mais agradáveis de escrever do que cadeias de chamadas <code>then</code>.</p>
<h2 id="generator"><a class="h_ident" id="h-iFWRauOs20" href="#h-iFWRauOs20" tabindex="-1" role="presentation"></a>Geradores</h2>
<p><a class="p_ident" id="p-Nk1VZ1mrKZ" href="#p-Nk1VZ1mrKZ" tabindex="-1" role="presentation"></a>Essa capacidade de funções serem pausadas e depois retomadas não é exclusiva de funções <code>async</code>. O JavaScript também tem um recurso chamado funções <em>geradoras</em>. Essas são semelhantes, mas sem as <em>promises</em>.</p>
<p><a class="p_ident" id="p-0xS4YpDJPe" href="#p-0xS4YpDJPe" tabindex="-1" role="presentation"></a>Quando você define uma função com <code>function*</code> (colocando um asterisco após a palavra <code>function</code>), ela se torna um gerador. Quando você chama um gerador, ele retorna um iterador, que já vimos no <a href="06_object.html">Capítulo 6</a>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-B4ek89g871" href="#c-B4ek89g871" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span><span class="tok-keyword">*</span> <span class="tok-definition">powers</span>(<span class="tok-definition">n</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">current</span> = n;; current *= n) {
<span class="tok-keyword">yield</span> current;
}
}
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">power</span> <span class="tok-keyword">of</span> powers(<span class="tok-number">3</span>)) {
<span class="tok-keyword">if</span> (power > <span class="tok-number">50</span>) <span class="tok-keyword">break</span>;
console.log(power);
}
<span class="tok-comment">// → 3</span>
<span class="tok-comment">// → 9</span>
<span class="tok-comment">// → 27</span></pre>
<p><a class="p_ident" id="p-fJwTaKnTiu" href="#p-fJwTaKnTiu" tabindex="-1" role="presentation"></a>Inicialmente, quando você chama <code>powers</code>, a função é congelada em seu início. Toda vez que você chama <code>next</code> no iterador, a função executa até atingir uma expressão <code>yield</code>, que a pausa e faz com que o valor <em>yielded</em> se torne o próximo valor produzido pelo iterador. Quando a função retorna (a do exemplo nunca retorna), o iterador está concluído.</p>
<p><a class="p_ident" id="p-ET9Rrp6peT" href="#p-ET9Rrp6peT" tabindex="-1" role="presentation"></a>Escrever iteradores é frequentemente muito mais fácil quando se usam funções geradoras. O iterador para a classe <code>Group</code> (do exercício no <a href="06_object.html#group_iterator">Capítulo 6</a>) pode ser escrito com este gerador:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-C6OZWjI9EM" href="#c-C6OZWjI9EM" tabindex="-1" role="presentation"></a>Group.prototype[Symbol.iterator] = <span class="tok-keyword">function</span><span class="tok-keyword">*</span>() {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < <span class="tok-keyword">this</span>.members.length; i++) {
<span class="tok-keyword">yield</span> <span class="tok-keyword">this</span>.members[i];
}
};</pre>
<p><a class="p_ident" id="p-QQ/BLr5Jhj" href="#p-QQ/BLr5Jhj" tabindex="-1" role="presentation"></a>Não há mais necessidade de criar um objeto para armazenar o estado da iteração — geradores automaticamente salvam seu estado local toda vez que fazem <em>yield</em>.</p>
<p><a class="p_ident" id="p-JFYTeaRkqk" href="#p-JFYTeaRkqk" tabindex="-1" role="presentation"></a>Tais expressões <code>yield</code> podem ocorrer apenas diretamente na própria função geradora e não em uma função interna que você define dentro dela. O estado que um gerador salva ao fazer <em>yield</em> é apenas seu ambiente <em>local</em> e a posição onde fez <em>yield</em>.</p>
<p><a class="p_ident" id="p-CCjceO00R5" href="#p-CCjceO00R5" tabindex="-1" role="presentation"></a>Uma função <code>async</code> é um tipo especial de gerador. Ela produz uma <em>promise</em> quando chamada, que é resolvida quando retorna (termina) e rejeitada quando lança uma exceção. Sempre que faz <em>yield</em> (espera) uma <em>promise</em>, o resultado dessa <em>promise</em> (valor ou exceção lançada) é o resultado da expressão <code>await</code>.</p>
<h2><a class="h_ident" id="h-vKZnoTaOEL" href="#h-vKZnoTaOEL" tabindex="-1" role="presentation"></a>Um Projeto de Arte Corvídea</h2>
<p><a class="p_ident" id="p-5rvNyTHOjZ" href="#p-5rvNyTHOjZ" tabindex="-1" role="presentation"></a>Uma manhã, Carla acorda com um barulho desconhecido do asfalto do lado de fora de seu hangar. Pulando para a borda do telhado, ela vê que os humanos estão montando algo. Há muita fiação elétrica, um palco e algum tipo de grande parede preta sendo construída.</p>
<p><a class="p_ident" id="p-E9LJYFfEo5" href="#p-E9LJYFfEo5" tabindex="-1" role="presentation"></a>Sendo uma corvo curiosa, Carla examina a parede mais de perto. Ela parece consistir em vários dispositivos grandes com vidro frontal conectados a cabos. Na parte de trás, os dispositivos dizem “LedTec SIG-5030”.</p>
<p><a class="p_ident" id="p-Gtlii0jpp1" href="#p-Gtlii0jpp1" tabindex="-1" role="presentation"></a>Uma rápida pesquisa na internet revela um manual do usuário para esses dispositivos. Eles parecem ser placas de trânsito, com uma matriz programável de LEDs âmbar. A intenção dos humanos é provavelmente exibir algum tipo de informação neles durante seu evento. Curiosamente, as telas podem ser programadas por uma rede sem fio. Será que estão conectadas à rede local do prédio?</p>
<p><a class="p_ident" id="p-aK7pLnGY4t" href="#p-aK7pLnGY4t" tabindex="-1" role="presentation"></a>Cada dispositivo em uma rede recebe um <em>endereço IP</em>, que outros dispositivos podem usar para enviar mensagens a ele. Falamos mais sobre isso no <a href="13_browser.html">Capítulo 13</a>. Carla percebe que seus próprios telefones recebem endereços como <code>10.0.0.20</code> ou <code>10.0.0.33</code>. Pode valer a pena tentar enviar mensagens para todos esses endereços e ver se algum responde à interface descrita no manual das placas.</p>
<p><a class="p_ident" id="p-eSkXlNrZDV" href="#p-eSkXlNrZDV" tabindex="-1" role="presentation"></a>O <a href="18_http.html">Capítulo 18</a> mostra como fazer requisições reais em redes reais. Neste capítulo, usaremos uma função simplificada fictícia chamada <code>request</code> para comunicação de rede. Esta função recebe dois argumentos — um endereço de rede e uma mensagem, que pode ser qualquer coisa que possa ser enviada como JSON — e retorna uma <em>promise</em> que resolve para uma resposta da máquina no endereço dado, ou rejeita se houve um problema.</p>
<p><a class="p_ident" id="p-pX3kdZkAEP" href="#p-pX3kdZkAEP" tabindex="-1" role="presentation"></a>De acordo com o manual, você pode mudar o que é exibido em uma placa SIG-5030 enviando uma mensagem com conteúdo como <code>{"command": "display", "data": [0, 0, 3, …]}</code>, onde <code>data</code> contém um número por ponto LED, fornecendo seu brilho — 0 significa desligado, 3 significa brilho máximo. Cada placa tem 50 luzes de largura e 30 de altura, então um comando de atualização deve enviar 1.500 números.</p>
<p><a class="p_ident" id="p-95VorWkYQN" href="#p-95VorWkYQN" tabindex="-1" role="presentation"></a>Este código envia uma mensagem de atualização de exibição para todos os endereços na rede local, para ver o que pega. Cada um dos números em um endereço IP pode ir de 0 a 255. Nos dados enviados, ele ativa um número de luzes correspondente ao último número do endereço de rede.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Q+JkLGXY9f" href="#c-Q+JkLGXY9f" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">addr</span> = <span class="tok-number">1</span>; addr < <span class="tok-number">256</span>; addr++) {
<span class="tok-keyword">let</span> <span class="tok-definition">data</span> = [];
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">n</span> = <span class="tok-number">0</span>; n < <span class="tok-number">1500</span>; n++) {
data.push(n < addr ? <span class="tok-number">3</span> : <span class="tok-number">0</span>);
}
<span class="tok-keyword">let</span> <span class="tok-definition">ip</span> = <span class="tok-string2">`10.0.0.</span>${addr}<span class="tok-string2">`</span>;
request(ip, {<span class="tok-definition">command</span>: <span class="tok-string">"display"</span>, <span class="tok-definition">data</span>})
.then(() => console.log(<span class="tok-string2">`Request to </span>${ip}<span class="tok-string2"> accepted`</span>))
.catch(() => {});
}</pre>
<p><a class="p_ident" id="p-IxS8v6F+fn" href="#p-IxS8v6F+fn" tabindex="-1" role="presentation"></a>Como a maioria desses endereços não existirá ou não aceitará tais mensagens, a chamada <code>catch</code> garante que erros de rede não travem o programa. As requisições são todas enviadas imediatamente, sem esperar que outras terminem, para não desperdiçar tempo quando algumas máquinas não respondem.</p>
<p><a class="p_ident" id="p-zj0rqlVnrv" href="#p-zj0rqlVnrv" tabindex="-1" role="presentation"></a>Tendo disparado sua varredura de rede, Carla volta para fora para ver o resultado. Para sua alegria, todas as telas agora mostram uma faixa de luz em seus cantos superiores esquerdos. Elas <em>estão</em> na rede local e <em>aceitam</em> comandos. Ela rapidamente anota os números mostrados em cada tela. Há nove telas, dispostas em três de altura por três de largura. Elas têm os seguintes endereços de rede:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-YxTIbNKE+b" href="#c-YxTIbNKE+b" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">screenAddresses</span> = [
<span class="tok-string">"10.0.0.44"</span>, <span class="tok-string">"10.0.0.45"</span>, <span class="tok-string">"10.0.0.41"</span>,
<span class="tok-string">"10.0.0.31"</span>, <span class="tok-string">"10.0.0.40"</span>, <span class="tok-string">"10.0.0.42"</span>,
<span class="tok-string">"10.0.0.48"</span>, <span class="tok-string">"10.0.0.47"</span>, <span class="tok-string">"10.0.0.46"</span>
];</pre>
<p><a class="p_ident" id="p-qOL8Z9xua9" href="#p-qOL8Z9xua9" tabindex="-1" role="presentation"></a>Agora isso abre possibilidades para todo tipo de travessura. Ela poderia mostrar “corvos mandam, humanos babam” na parede em letras gigantes. Mas isso parece um pouco grosseiro. Em vez disso, ela planeja mostrar um vídeo de uma corvo voando cobrindo todas as telas à noite.</p>
<p><a class="p_ident" id="p-z8NLZPN21x" href="#p-z8NLZPN21x" tabindex="-1" role="presentation"></a>Carla encontra um clipe de vídeo adequado, no qual um segundo e meio de filmagem pode ser repetido para criar um vídeo em <em>loop</em> mostrando a batida de asa de uma corvo. Para caber nas nove telas (cada uma podendo mostrar 50x30 pixels), Carla corta e redimensiona os vídeos para obter uma série de imagens de 150x90, 10 por segundo. Cada uma é então cortada em nove retângulos e processada de modo que os pontos escuros no vídeo (onde a corvo está) mostrem uma luz brilhante, e os pontos claros (sem corvo) fiquem escuros, o que deve criar o efeito de uma corvo âmbar voando contra um fundo preto.</p>
<p><a class="p_ident" id="p-e0+u2hhq8g" href="#p-e0+u2hhq8g" tabindex="-1" role="presentation"></a>Ela configurou a variável <code>clipImages</code> para conter um <em>array</em> de quadros, onde cada quadro é representado com um <em>array</em> de nove conjuntos de pixels — um para cada tela — no formato que as placas esperam.</p>
<p><a class="p_ident" id="p-EoNQn91Y5T" href="#p-EoNQn91Y5T" tabindex="-1" role="presentation"></a>Para exibir um único quadro do vídeo, Carla precisa enviar uma requisição para todas as telas ao mesmo tempo. Mas ela também precisa esperar pelo resultado dessas requisições, tanto para não começar a enviar o próximo quadro antes que o atual tenha sido adequadamente enviado quanto para perceber quando requisições estão falhando.</p>
<p><a class="p_ident" id="p-fBpM0P24NQ" href="#p-fBpM0P24NQ" tabindex="-1" role="presentation"></a><code>Promise</code> tem um método estático <code>all</code> que pode ser usado para converter um <em>array</em> de <em>promises</em> em uma única <em>promise</em> que resolve para um <em>array</em> de resultados. Isso fornece uma maneira conveniente de ter algumas ações assíncronas acontecendo lado a lado, esperar que todas terminem e então fazer algo com seus resultados (ou pelo menos esperar por elas para garantir que não falhem).</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IQcpgoIvEH" href="#c-IQcpgoIvEH" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">displayFrame</span>(<span class="tok-definition">frame</span>) {
<span class="tok-keyword">return</span> Promise.all(frame.map((<span class="tok-definition">data</span>, <span class="tok-definition">i</span>) => {
<span class="tok-keyword">return</span> request(screenAddresses[i], {
<span class="tok-definition">command</span>: <span class="tok-string">"display"</span>,
<span class="tok-definition">data</span>
});
}));
}</pre>
<p><a class="p_ident" id="p-S8nWMwk+tb" href="#p-S8nWMwk+tb" tabindex="-1" role="presentation"></a>Isso mapeia sobre as imagens em <code>frame</code> (que é um <em>array</em> de <em>arrays</em> de dados de exibição) para criar um <em>array</em> de <em>promises</em> de requisição. Então retorna uma <em>promise</em> que combina todas elas.</p>
<p><a class="p_ident" id="p-x0tM0YEkbG" href="#p-x0tM0YEkbG" tabindex="-1" role="presentation"></a>Para poder parar um vídeo em reprodução, o processo é envolvido em uma classe. Essa classe tem um método assíncrono <code>play</code> que retorna uma <em>promise</em> que resolve apenas quando a reprodução é parada novamente via o método <code>stop</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9CWs6kw1Nf" href="#c-9CWs6kw1Nf" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">wait</span>(<span class="tok-definition">time</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">accept</span> => setTimeout(accept, time));
}
<span class="tok-keyword">class</span> VideoPlayer {
<span class="tok-definition">constructor</span>(<span class="tok-definition">frames</span>, <span class="tok-definition">frameTime</span>) {
<span class="tok-keyword">this</span>.frames = frames;
<span class="tok-keyword">this</span>.frameTime = frameTime;
<span class="tok-keyword">this</span>.stopped = true;
}
<span class="tok-keyword">async</span> <span class="tok-definition">play</span>() {
<span class="tok-keyword">this</span>.stopped = false;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; !<span class="tok-keyword">this</span>.stopped; i++) {
<span class="tok-keyword">let</span> <span class="tok-definition">nextFrame</span> = wait(<span class="tok-keyword">this</span>.frameTime);
<span class="tok-keyword">await</span> displayFrame(<span class="tok-keyword">this</span>.frames[i % <span class="tok-keyword">this</span>.frames.length]);
<span class="tok-keyword">await</span> nextFrame;
}
}
<span class="tok-definition">stop</span>() {
<span class="tok-keyword">this</span>.stopped = true;
}
}</pre>
<p><a class="p_ident" id="p-yQZD+zR2e5" href="#p-yQZD+zR2e5" tabindex="-1" role="presentation"></a>A função <code>wait</code> envolve <code>setTimeout</code> em uma <em>promise</em> que resolve após o número dado de milissegundos. Isso é útil para controlar a velocidade de reprodução.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AsRdO9dD+T" href="#c-AsRdO9dD+T" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">video</span> = <span class="tok-keyword">new</span> VideoPlayer(clipImages, <span class="tok-number">100</span>);
video.play().catch(<span class="tok-definition">e</span> => {
console.log(<span class="tok-string">"Playback failed: "</span> + e);
});
setTimeout(() => video.stop(), <span class="tok-number">15000</span>);</pre>
<p><a class="p_ident" id="p-PDneyGDCSu" href="#p-PDneyGDCSu" tabindex="-1" role="presentation"></a>Durante toda a semana que a parede de telas fica de pé, toda noite, quando escurece, um enorme pássaro laranja brilhante misteriosamente aparece nela.</p>
<h2><a class="h_ident" id="h-Afuo5P2CCa" href="#h-Afuo5P2CCa" tabindex="-1" role="presentation"></a>O loop de eventos</h2>
<p><a class="p_ident" id="p-CktVb8Q3Q1" href="#p-CktVb8Q3Q1" tabindex="-1" role="presentation"></a>Um programa assíncrono começa executando seu script principal, que frequentemente configura <em>callbacks</em> para serem chamados depois. Esse script principal, assim como os <em>callbacks</em>, executam até o fim de uma só vez, sem interrupção. Mas entre eles, o programa pode ficar ocioso, esperando algo acontecer.</p>
<p><a class="p_ident" id="p-bPgB3hL7bB" href="#p-bPgB3hL7bB" tabindex="-1" role="presentation"></a>Então <em>callbacks</em> não são chamados diretamente pelo código que os agendou. Se eu chamar <code>setTimeout</code> de dentro de uma função, aquela função já terá retornado no momento em que a função de <em>callback</em> é chamada. E quando o <em>callback</em> retorna, o controle não volta para a função que o agendou.</p>
<p><a class="p_ident" id="p-w5KQK091ZE" href="#p-w5KQK091ZE" tabindex="-1" role="presentation"></a>O comportamento assíncrono acontece em sua própria pilha de chamadas vazia. Esta é uma das razões pelas quais, sem <em>promises</em>, gerenciar exceções através de código assíncrono é tão difícil. Como cada <em>callback</em> começa com uma pilha praticamente vazia, seus handlers <code>catch</code> não estarão na pilha quando lançarem uma exceção.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-otqSpf5dWA" href="#c-otqSpf5dWA" tabindex="-1" role="presentation"></a><span class="tok-keyword">try</span> {
setTimeout(() => {
<span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string">"Woosh"</span>);
}, <span class="tok-number">20</span>);
} <span class="tok-keyword">catch</span> (<span class="tok-definition">e</span>) {
<span class="tok-comment">// Isso não executará</span>
console.log(<span class="tok-string">"Caught"</span>, e);
}</pre>
<p><a class="p_ident" id="p-5Gdz3FK8G4" href="#p-5Gdz3FK8G4" tabindex="-1" role="presentation"></a>Não importa quão próximos no tempo eventos — como timeouts ou requisições recebidas — aconteçam, um ambiente JavaScript executará apenas um programa por vez. Você pode pensar nisso como ele executando um grande <em>loop</em> <em>ao redor</em> do seu programa, chamado de <em>loop de eventos</em>. Quando não há nada a ser feito, esse <em>loop</em> é pausado. Mas conforme eventos chegam, eles são adicionados a uma fila, e seu código é executado um após o outro. Como nada executa ao mesmo tempo, código de execução lenta pode atrasar o tratamento de outros eventos.</p>
<p><a class="p_ident" id="p-yoWhr016pc" href="#p-yoWhr016pc" tabindex="-1" role="presentation"></a>Este exemplo define um timeout mas então demora além do momento pretendido do timeout, fazendo o timeout atrasar.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Xozg9CCvVZ" href="#c-Xozg9CCvVZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">start</span> = Date.now();
setTimeout(() => {
console.log(<span class="tok-string">"Timeout ran at"</span>, Date.now() - start);
}, <span class="tok-number">20</span>);
<span class="tok-keyword">while</span> (Date.now() < start + <span class="tok-number">50</span>) {}
console.log(<span class="tok-string">"Wasted time until"</span>, Date.now() - start);
<span class="tok-comment">// → Wasted time until 50</span>
<span class="tok-comment">// → Timeout ran at 55</span></pre>
<p><a class="p_ident" id="p-bIUpdJBhfL" href="#p-bIUpdJBhfL" tabindex="-1" role="presentation"></a><em>Promises</em> sempre resolvem ou rejeitam como um novo evento. Mesmo se uma <em>promise</em> já estiver resolvida, esperar por ela fará com que seu <em>callback</em> execute após o script atual terminar, em vez de imediatamente.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-B4TYo0a2ol" href="#c-B4TYo0a2ol" tabindex="-1" role="presentation"></a>Promise.resolve(<span class="tok-string">"Done"</span>).then(console.log);
console.log(<span class="tok-string">"Me first!"</span>);
<span class="tok-comment">// → Me first!</span>
<span class="tok-comment">// → Done</span></pre>
<p><a class="p_ident" id="p-6pppYHmP9F" href="#p-6pppYHmP9F" tabindex="-1" role="presentation"></a>Em capítulos posteriores veremos vários outros tipos de eventos que executam no <em>loop</em> de eventos.</p>
<h2><a class="h_ident" id="h-f9tFyOqtWP" href="#h-f9tFyOqtWP" tabindex="-1" role="presentation"></a>Bugs assíncronos</h2>
<p><a class="p_ident" id="p-AtULhUfF99" href="#p-AtULhUfF99" tabindex="-1" role="presentation"></a>Quando seu programa executa sincronamente, de uma só vez, não há mudanças de estado acontecendo exceto aquelas que o próprio programa faz. Para programas assíncronos isso é diferente — eles podem ter <em>lacunas</em> em sua execução durante as quais outro código pode executar.</p>
<p><a class="p_ident" id="p-ZKlr60CO6M" href="#p-ZKlr60CO6M" tabindex="-1" role="presentation"></a>Vejamos um exemplo. Esta é uma função que tenta reportar o tamanho de cada arquivo em um <em>array</em> de arquivos, garantindo que todos sejam lidos ao mesmo tempo em vez de em sequência.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Jylqrewj4x" href="#c-Jylqrewj4x" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">fileSizes</span>(<span class="tok-definition">files</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">list</span> = <span class="tok-string">""</span>;
<span class="tok-keyword">await</span> Promise.all(files.map(<span class="tok-keyword">async</span> <span class="tok-definition">fileName</span> => {
list += fileName + <span class="tok-string">": "</span> +
(<span class="tok-keyword">await</span> textFile(fileName)).length + <span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>;
}));
<span class="tok-keyword">return</span> list;
}</pre>
<p><a class="p_ident" id="p-Mp9Z6u34q7" href="#p-Mp9Z6u34q7" tabindex="-1" role="presentation"></a>A parte <code>async fileName =></code> mostra como arrow functions também podem ser tornadas <code>async</code> colocando a palavra <code>async</code> na frente delas.</p>
<p><a class="p_ident" id="p-cA+SiYEXys" href="#p-cA+SiYEXys" tabindex="-1" role="presentation"></a>O código não parece imediatamente suspeito... ele mapeia a <em>arrow function</em> <code>async</code> sobre o <em>array</em> de nomes, criando um <em>array</em> de <em>promises</em>, e então usa <code>Promise.all</code> para esperar por todas antes de retornar a lista que constroem.</p>
<p><a class="p_ident" id="p-0cLaFghNpB" href="#p-0cLaFghNpB" tabindex="-1" role="presentation"></a>Mas esse programa está completamente quebrado. Ele sempre retornará apenas uma única linha de saída, listando o arquivo que levou mais tempo para ser lido.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KqsJmHqBh2" href="#c-KqsJmHqBh2" tabindex="-1" role="presentation"></a>fileSizes([<span class="tok-string">"plans.txt"</span>, <span class="tok-string">"shopping_list.txt"</span>])
.then(console.log);</pre>
<p><a class="p_ident" id="p-Y4KbaieINu" href="#p-Y4KbaieINu" tabindex="-1" role="presentation"></a>Você consegue descobrir por quê?</p>
<p><a class="p_ident" id="p-+o40wDs8yC" href="#p-+o40wDs8yC" tabindex="-1" role="presentation"></a>O problema está no operador <code>+=</code>, que pega o valor <em>atual</em> de <code>list</code> no momento em que a declaração começa a executar e então, quando o <code>await</code> termina, define a <em>binding</em> <code>list</code> como aquele valor mais a <em>string</em> adicionada.</p>
<p><a class="p_ident" id="p-ko9LoCDH46" href="#p-ko9LoCDH46" tabindex="-1" role="presentation"></a>Mas entre o momento em que a declaração começa a executar e o momento em que termina, há uma lacuna assíncrona. A expressão <code>map</code> executa antes de qualquer coisa ter sido adicionada à lista, então cada um dos operadores <code>+=</code> começa de uma <em>string</em> vazia e acaba, quando seu armazenamento termina, definindo <code>list</code> como o resultado de adicionar sua linha à <em>string</em> vazia.</p>
<p><a class="p_ident" id="p-+uZWiwr+Xu" href="#p-+uZWiwr+Xu" tabindex="-1" role="presentation"></a>Isso poderia ter sido facilmente evitado retornando as linhas das <em>promises</em> mapeadas e chamando <code>join</code> no resultado de <code>Promise.all</code>, em vez de construir a lista alterando uma <em>binding</em>. Como de costume, computar novos valores é menos propenso a erros do que alterar valores existentes.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-gHyR38RcpA" href="#c-gHyR38RcpA" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">fileSizes</span>(<span class="tok-definition">files</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">lines</span> = files.map(<span class="tok-keyword">async</span> <span class="tok-definition">fileName</span> => {
<span class="tok-keyword">return</span> fileName + <span class="tok-string">": "</span> +
(<span class="tok-keyword">await</span> textFile(fileName)).length;
});
<span class="tok-keyword">return</span> (<span class="tok-keyword">await</span> Promise.all(lines)).join(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>);
}</pre>
<p><a class="p_ident" id="p-+zfXT48pAB" href="#p-+zfXT48pAB" tabindex="-1" role="presentation"></a>Erros como esse são fáceis de cometer, especialmente quando se usa <code>await</code>, e você deve estar ciente de onde estão as lacunas em seu código. Uma vantagem da assincronicidade <em>explícita</em> do JavaScript (seja através de <em>callbacks</em>, <em>promises</em> ou <code>await</code>) é que identificar essas lacunas é relativamente fácil.</p>
<h2><a class="h_ident" id="h-741R75mvqx" href="#h-741R75mvqx" tabindex="-1" role="presentation"></a>Resumo</h2>
<p><a class="p_ident" id="p-LBYEj722Gu" href="#p-LBYEj722Gu" tabindex="-1" role="presentation"></a>A programação assíncrona torna possível expressar a espera por ações de longa duração sem congelar o programa todo. Ambientes JavaScript tipicamente implementam esse estilo de programação usando <em>callbacks</em>, funções que são chamadas quando as ações completam. Um <em>loop</em> de eventos agenda tais <em>callbacks</em> para serem chamados quando apropriado, um após o outro, para que suas execuções não se sobreponham.</p>
<p><a class="p_ident" id="p-oS273NVZbU" href="#p-oS273NVZbU" tabindex="-1" role="presentation"></a>Programar assincronamente é facilitado por <em>promises</em>, objetos que representam ações que podem completar no futuro, e funções <code>async</code>, que permitem escrever um programa assíncrono como se fosse síncrono.</p>
<h2><a class="h_ident" id="h-0CpJUZuhQJ" href="#h-0CpJUZuhQJ" tabindex="-1" role="presentation"></a>Exercícios</h2>
<h3><a class="i_ident" id="i-2iPYe1RdIh" href="#i-2iPYe1RdIh" tabindex="-1" role="presentation"></a>Tempos Tranquilos</h3>
<p><a class="p_ident" id="p-PheezMSGYJ" href="#p-PheezMSGYJ" tabindex="-1" role="presentation"></a>Há uma câmera de segurança perto do laboratório de Carla que é ativada por um sensor de movimento. Ela está conectada à rede e começa a enviar um fluxo de vídeo quando está ativa. Como prefere não ser descoberta, Carla montou um sistema que percebe esse tipo de tráfego de rede sem fio e acende uma luz em seu esconderijo sempre que há atividade do lado de fora, para que ela saiba quando ficar quieta.</p>
<p><a class="p_ident" id="p-yLarPA3us0" href="#p-yLarPA3us0" tabindex="-1" role="presentation"></a>Ela também tem registrado os horários em que a câmera é acionada por um tempo e quer usar essas informações para visualizar quais horários, em uma semana média, tendem a ser tranquilos e quais tendem a ser movimentados. O registro é armazenado em arquivos contendo um <em>timestamp</em> (como retornado por <code>Date.now()</code>) por linha.</p>
<pre class="snippet" data-language="null" ><a class="c_ident" id="c-oYpYSTh/9y" href="#c-oYpYSTh/9y" tabindex="-1" role="presentation"></a>1695709940692
1695701068331
1695701189163</pre>
<p><a class="p_ident" id="p-ysN7FnQMsO" href="#p-ysN7FnQMsO" tabindex="-1" role="presentation"></a>O arquivo <code>"camera_logs.<wbr>txt"</code> contém uma lista de arquivos de log. Escreva uma função assíncrona <code>activityTable(day)</code> que para um dado dia da semana retorna um <em>array</em> de 24 números, um para cada hora do dia, que contém o número de observações de tráfego de rede da câmera vistas naquela hora do dia. Dias são identificados por número usando o sistema de <code>Date.getDay</code>, onde domingo é 0 e sábado é 6.</p>
<p><a class="p_ident" id="p-JsvqC8WkAW" href="#p-JsvqC8WkAW" tabindex="-1" role="presentation"></a>A função <code>activityGraph</code>, fornecida pela <em>sandbox</em>, resume tal tabela em uma <em>string</em>.</p>
<p><a class="p_ident" id="p-8avSfZdyZS" href="#p-8avSfZdyZS" tabindex="-1" role="presentation"></a>Para ler os arquivos, use a função <code>textFile</code> definida anteriormente — dado um nome de arquivo, ela retorna uma <em>promise</em> que resolve para o conteúdo do arquivo. Lembre-se de que <code>new Date(timestamp)</code> cria um objeto <code>Date</code> para aquele momento, que tem métodos <code>getDay</code> e <code>getHours</code> retornando o dia da semana e a hora do dia.</p>
<p><a class="p_ident" id="p-bG61DvTQAv" href="#p-bG61DvTQAv" tabindex="-1" role="presentation"></a>Ambos os tipos de arquivos — a lista de arquivos de log e os arquivos de log em si — têm cada dado em sua própria linha, separados por caracteres de nova linha (<code>"\n"</code>).</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-f8klWqoByk" href="#c-f8klWqoByk" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">day</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">logFileList</span> = <span class="tok-keyword">await</span> textFile(<span class="tok-string">"camera_logs.txt"</span>);
<span class="tok-comment">// Seu código aqui</span>
}
activityTable(<span class="tok-number">1</span>)
.then(<span class="tok-definition">table</span> => console.log(activityGraph(table)));</pre>
<details class="solution"><summary>Display hints...</summary><div class="solution-text">
<p><a class="p_ident" id="p-SZFDgeeW6m" href="#p-SZFDgeeW6m" tabindex="-1" role="presentation"></a>Você precisará converter o conteúdo desses arquivos em um <em>array</em>. A maneira mais fácil de fazer isso é usar o método <code>split</code> na <em>string</em> produzida por <code>textFile</code>. Note que para os arquivos de log, isso ainda dará um <em>array</em> de <em>strings</em>, que você precisa converter em números antes de passá-los para <code>new Date</code>.</p>
<p><a class="p_ident" id="p-VYt4YJI9uN" href="#p-VYt4YJI9uN" tabindex="-1" role="presentation"></a>Resumir todos os pontos de tempo em uma tabela de horas pode ser feito criando uma tabela (<em>array</em>) que contém um número para cada hora do dia. Você pode então iterar sobre todos os <em>timestamps</em> (sobre os arquivos de log e os números em cada arquivo de log) e para cada um, se aconteceu no dia correto, pegar a hora em que ocorreu e somar um ao número correspondente na tabela.</p>
<p><a class="p_ident" id="p-+HV/2PPoOt" href="#p-+HV/2PPoOt" tabindex="-1" role="presentation"></a>Certifique-se de usar <code>await</code> no resultado de funções assíncronas antes de fazer qualquer coisa com ele, ou você acabará com uma <code>Promise</code> onde esperava uma <em>string</em>.</p>
</div></details>
<h3><a class="i_ident" id="i-1M3rN7g5Hf" href="#i-1M3rN7g5Hf" tabindex="-1" role="presentation"></a>Promises Reais</h3>
<p><a class="p_ident" id="p-QwZQ4ihxnO" href="#p-QwZQ4ihxnO" tabindex="-1" role="presentation"></a>Reescreva a função do exercício anterior sem <code>async</code>/<code>await</code>, usando métodos simples de <code>Promise</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ngGsqJFKXS" href="#c-ngGsqJFKXS" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">day</span>) {
<span class="tok-comment">// Seu código aqui</span>
}
activityTable(<span class="tok-number">6</span>)
.then(<span class="tok-definition">table</span> => console.log(activityGraph(table)));</pre>
<p><a class="p_ident" id="p-bEAx/jBnYy" href="#p-bEAx/jBnYy" tabindex="-1" role="presentation"></a>Neste estilo, usar <code>Promise.all</code> será mais conveniente do que tentar modelar um <em>loop</em> sobre os arquivos de log. Na função <code>async</code>, simplesmente usar <code>await</code> em um <em>loop</em> é mais simples. Se ler um arquivo leva algum tempo, qual dessas duas abordagens levará menos tempo para executar?</p>
<p><a class="p_ident" id="p-Go/nST4Kup" href="#p-Go/nST4Kup" tabindex="-1" role="presentation"></a>Se um dos arquivos listados na lista de arquivos tiver um erro de digitação e a leitura falhar, como essa falha acaba no objeto <code>Promise</code> que sua função retorna?</p>
<details class="solution"><summary>Display hints...</summary><div class="solution-text">
<p><a class="p_ident" id="p-4bLsUuZKww" href="#p-4bLsUuZKww" tabindex="-1" role="presentation"></a>A abordagem mais direta para escrever esta função é usar uma cadeia de chamadas <code>then</code>. A primeira <em>promise</em> é produzida lendo a lista de arquivos de log. O primeiro <em>callback</em> pode dividir essa lista e mapear <code>textFile</code> sobre ela para obter um <em>array</em> de <em>promises</em> para passar a <code>Promise.all</code>. Pode retornar o objeto retornado por <code>Promise.all</code>, de modo que o que quer que aquilo retorne se torne o resultado do valor de retorno deste primeiro <code>then</code>.</p>
<p><a class="p_ident" id="p-JKGQXVmxLU" href="#p-JKGQXVmxLU" tabindex="-1" role="presentation"></a>Agora temos uma <em>promise</em> que retorna um <em>array</em> de arquivos de log. Podemos chamar <code>then</code> novamente nela e colocar a lógica de contagem de <em>timestamps</em> ali. Algo assim:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-k5fzhKj30C" href="#c-k5fzhKj30C" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">day</span>) {
<span class="tok-keyword">return</span> textFile(<span class="tok-string">"camera_logs.txt"</span>).then(<span class="tok-definition">files</span> => {
<span class="tok-keyword">return</span> Promise.all(files.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(textFile));
}).then(<span class="tok-definition">logs</span> => {
<span class="tok-comment">// analisar...</span>
});
}</pre>
<p><a class="p_ident" id="p-A/5nEV6b9Y" href="#p-A/5nEV6b9Y" tabindex="-1" role="presentation"></a>Ou você poderia, para um agendamento de trabalho ainda melhor, colocar a análise de cada arquivo dentro de <code>Promise.all</code>, de modo que esse trabalho possa ser iniciado para o primeiro arquivo que voltar do disco, antes mesmo dos outros arquivos voltarem.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lDFD5iOUBP" href="#c-lDFD5iOUBP" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">day</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">table</span> = []; <span class="tok-comment">// inicializar...</span>
<span class="tok-keyword">return</span> textFile(<span class="tok-string">"camera_logs.txt"</span>).then(<span class="tok-definition">files</span> => {
<span class="tok-keyword">return</span> Promise.all(files.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(<span class="tok-definition">name</span> => {
<span class="tok-keyword">return</span> textFile(name).then(<span class="tok-definition">log</span> => {
<span class="tok-comment">// analisar...</span>
});
}));
}).then(() => table);
}</pre>
<p><a class="p_ident" id="p-ct6m16dGhH" href="#p-ct6m16dGhH" tabindex="-1" role="presentation"></a>Isso mostra que a maneira como você estrutura suas <em>promises</em> pode ter um efeito real na maneira como o trabalho é agendado. Um simples <em>loop</em> com <code>await</code> torna o processo completamente linear — espera cada arquivo carregar antes de prosseguir. <code>Promise.all</code> torna possível que múltiplas tarefas sejam conceitualmente trabalhadas ao mesmo tempo, permitindo-lhes progredir enquanto arquivos ainda estão sendo carregados. Isso pode ser mais rápido, mas também torna a ordem em que as coisas acontecerão menos previsível. Neste caso, estamos apenas incrementando números em uma tabela, o que não é difícil de fazer de forma segura. Para outros tipos de problemas, pode ser muito mais difícil.</p>
<p><a class="p_ident" id="p-qoFt9fwWQm" href="#p-qoFt9fwWQm" tabindex="-1" role="presentation"></a>Quando um arquivo na lista não existe, a <em>promise</em> retornada por <code>textFile</code> será rejeitada. Como <code>Promise.all</code> rejeita se qualquer uma das <em>promises</em> dadas a ela falhar, o valor de retorno do <em>callback</em> dado ao primeiro <code>then</code> também será uma <em>promise</em> rejeitada. Isso faz a <em>promise</em> retornada por <code>then</code> falhar, então o <em>callback</em> dado ao segundo <code>then</code> nem sequer é chamado, e uma <em>promise</em> rejeitada é retornada da função.</p>
</div></details>
<h3><a class="i_ident" id="i-7xIxHD82Rz" href="#i-7xIxHD82Rz" tabindex="-1" role="presentation"></a>Construindo Promise.all</h3>
<p><a class="p_ident" id="p-vLxG2vQx+a" href="#p-vLxG2vQx+a" tabindex="-1" role="presentation"></a>Como vimos, dado um <em>array</em> de promises, <code>Promise.all</code> retorna uma <em>promise</em> que espera todas as <em>promises</em> no <em>array</em> terminarem. Ela então tem sucesso, produzindo um <em>array</em> de valores resultado. Se uma <em>promise</em> no <em>array</em> falhar, a <em>promise</em> retornada por <code>all</code> também falha, passando adiante a razão da falha da <em>promise</em> que falhou.</p>
<p><a class="p_ident" id="p-l4b07ToCzc" href="#p-l4b07ToCzc" tabindex="-1" role="presentation"></a>Implemente algo assim você mesmo como uma função regular chamada <code>Promise_all</code>.</p>
<p><a class="p_ident" id="p-WHfa894xRI" href="#p-WHfa894xRI" tabindex="-1" role="presentation"></a>Lembre-se de que depois que uma <em>promise</em> tem sucesso ou falha, ela não pode ter sucesso ou falhar novamente, e chamadas adicionais às funções que a resolvem são ignoradas. Isso pode simplificar a maneira como você lida com a falha da sua <em>promise</em>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-c7w6SgQ96R" href="#c-c7w6SgQ96R" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">Promise_all</span>(<span class="tok-definition">promises</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => {
<span class="tok-comment">// Seu código aqui.</span>
});
}
<span class="tok-comment">// Código de teste.</span>
Promise_all([]).then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"This should be []:"</span>, array);
});
<span class="tok-keyword">function</span> <span class="tok-definition">soon</span>(<span class="tok-definition">val</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">resolve</span> => {
setTimeout(() => resolve(val), Math.random() * <span class="tok-number">500</span>);
});
}
Promise_all([soon(<span class="tok-number">1</span>), soon(<span class="tok-number">2</span>), soon(<span class="tok-number">3</span>)]).then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"This should be [1, 2, 3]:"</span>, array);
});
Promise_all([soon(<span class="tok-number">1</span>), Promise.reject(<span class="tok-string">"X"</span>), soon(<span class="tok-number">3</span>)])
.then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"We should not get here"</span>);
})
.catch(<span class="tok-definition">error</span> => {
<span class="tok-keyword">if</span> (error != <span class="tok-string">"X"</span>) {
console.log(<span class="tok-string">"Unexpected failure:"</span>, error);
}
});</pre>
<details class="solution"><summary>Display hints...</summary><div class="solution-text">
<p><a class="p_ident" id="p-UAk/Nl7ynJ" href="#p-UAk/Nl7ynJ" tabindex="-1" role="presentation"></a>A função passada ao construtor <code>Promise</code> terá que chamar <code>then</code> em cada uma das <em>promises</em> no <em>array</em> dado. Quando uma delas tem sucesso, duas coisas precisam acontecer. O valor resultante precisa ser armazenado na posição correta de um <em>array</em> de resultados, e precisamos verificar se esta era a última promise pendente e finalizar nossa própria <em>promise</em> se era.</p>
<p><a class="p_ident" id="p-JHxXluBU5k" href="#p-JHxXluBU5k" tabindex="-1" role="presentation"></a>Isso pode ser feito com um contador que é inicializado com o comprimento do <em>array</em> de entrada e do qual subtraímos 1 toda vez que uma <em>promise</em> tem sucesso. Quando chega a 0, terminamos. Certifique-se de levar em conta a situação onde o <em>array</em> de entrada está vazio (e portanto nenhuma <em>promise</em> jamais resolverá).</p>
<p><a class="p_ident" id="p-+OOjmrx0oM" href="#p-+OOjmrx0oM" tabindex="-1" role="presentation"></a>Lidar com falha requer alguma reflexão, mas acaba sendo extremamente simples. Basta passar a função <code>reject</code> da <em>promise</em> wrapper para cada uma das <em>promises</em> no <em>array</em> como handler de <code>catch</code> ou como segundo argumento para <code>then</code>, de modo que uma falha em uma delas dispare a rejeição da <em>promise</em> wrapper inteira.</p>
</div></details><nav><a href="10_modules.html" title="capítulo anterior" aria-label="capítulo anterior">◂</a> <a href="index.html" title="capa" aria-label="capa">●</a> <a href="12_language.html" title="próximo capítulo" aria-label="próximo capítulo">▸</a> <button class=help title="ajuda" aria-label="ajuda"><strong>?</strong></button>
</nav>
</article>
<script src="ejs.js"></script>