-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy path11_async.html
More file actions
648 lines (438 loc) · 89 KB
/
Copy path11_async.html
File metadata and controls
648 lines (438 loc) · 89 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
641
642
643
644
645
646
647
648
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Programación Asíncrona :: Eloquent JavaScript</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="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="12_language.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
<h1>Programación Asíncrona</h1>
<blockquote>
<p><a class="p_ident" id="p-50dHgvPbfK" href="#p-50dHgvPbfK" tabindex="-1" role="presentation"></a>¿Quién puede esperar en silencio mientras el barro se asienta?<br>¿Quién puede permanecer quieto hasta el momento de la acción?</p>
<footer>Laozi, <cite>Tao Te Ching</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_11.jpg" alt="Ilustración que muestra dos cuervos en una rama de árbol"></figure>
<p><a class="p_ident" id="p-VOCX31yYLX" href="#p-VOCX31yYLX" tabindex="-1" role="presentation"></a>La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama <em>procesador</em>. Los programas que hemos visto hasta ahora mantendrán ocupado al procesador hasta que hayan terminado su trabajo. La velocidad a la cual puede ser ejecutado algo como un bucle que manipula números depende casi enteramente de la velocidad del procesador y la memoria de la computadora.</p>
<p><a class="p_ident" id="p-hbyVo1hWzb" href="#p-hbyVo1hWzb" tabindex="-1" role="presentation"></a>Pero muchos programas interactúan con cosas fuera del procesador. Por ejemplo, pueden comunicarse a través de una red de computadoras o solicitar datos desde el disco duro, lo cual es mucho más lento que obtenerlos de la memoria.</p>
<p><a class="p_ident" id="p-sSdVomtbXO" href="#p-sSdVomtbXO" tabindex="-1" role="presentation"></a>Cuando esto está sucediendo, sería una lástima dejar el procesador inactivo: podría haber otro trabajo que este podría hacer en ese tiempo. En parte, esto es algo que maneja tu sistema operativo, el cual irá dándole al procesador múltiples programas en ejecución, haciendo que vaya cambiando entre ellos. Pero eso no ayuda cuando queremos que un <em>único</em> programa pueda avanzar mientras espera una solicitud de red.</p>
<h2><a class="h_ident" id="h-tfdiSubDV/" href="#h-tfdiSubDV/" tabindex="-1" role="presentation"></a>Asincronía</h2>
<p><a class="p_ident" id="p-hOUZrWQWAe" href="#p-hOUZrWQWAe" tabindex="-1" role="presentation"></a>En un modelo de programación <em>sincrónico</em>, las cosas suceden una a una. Cuando llamas a una función que realiza una acción de larga duración, esta solo retorna cuando la acción ha terminado y puede devolver su resultado. Esto detiene tu programa durante el tiempo que tome la acción.</p>
<p><a class="p_ident" id="p-IlgtHDuXQZ" href="#p-IlgtHDuXQZ" tabindex="-1" role="presentation"></a>Un modelo <em>asíncrono</em> permite que múltiples cosas sucedan al mismo tiempo. Cuando inicias una acción, tu programa continúa ejecutándose. Cuando la acción termina, el programa es informado y obtiene acceso al resultado (por ejemplo, los datos leídos desde el disco).</p>
<p><a class="p_ident" id="p-9kP4nAsEsK" href="#p-9kP4nAsEsK" tabindex="-1" role="presentation"></a>Podemos comparar la programación sincrónica y asíncrona usando un pequeño ejemplo: un programa que realiza dos solicitudes a través de la red y luego combina de algún modo los resultados.</p>
<p><a class="p_ident" id="p-lPwjR+DoPb" href="#p-lPwjR+DoPb" tabindex="-1" role="presentation"></a>En un entorno sincrónico, donde la función de solicitud retorna solo después de haber hecho su trabajo, la forma más fácil de realizar esta tarea es hacer las solicitudes una después de la otra. Esto tiene la desventaja de que la segunda solicitud se iniciará solo cuando la primera haya terminado. El tiempo total necesario será al menos la suma de los dos tiempos de respuesta.</p>
<p><a class="p_ident" id="p-dqKneyp1kQ" href="#p-dqKneyp1kQ" tabindex="-1" role="presentation"></a>La solución a este problema, en un sistema sincrónico, es iniciar hilos de control adicionales. Un <em>hilo</em> es otro programa en ejecución cuya ejecución puede ser intercalada con otros programas por el sistema operativo —como la mayoría de las computadoras modernas contienen múltiples procesadores, podrían ejecutarse incluso múltiples hilos al mismo tiempo, en diferentes procesadores. Un segundo hilo podría iniciar la segunda solicitud, y luego ambos hilos podrían esperar sus resultados, después de lo cual se resincronizan para combinarlos.</p>
<p><a class="p_ident" id="p-Wkrmfutpwr" href="#p-Wkrmfutpwr" tabindex="-1" role="presentation"></a>En el siguiente diagrama, las líneas gruesas representan el tiempo que el programa pasa funcionando normalmente, y las líneas delgadas representan el tiempo gastado esperando a la red. En el modelo sincrónico, el tiempo tomado por la red es <em>parte</em> de la línea de tiempo para un hilo de control dado. En el modelo asíncrono, iniciar una acción en la red permite que el programa continúe ejecutándose mientras la comunicación en la red sucede junto a él, notificando al programa cuando haya terminado.</p><figure><img src="img/control-io.svg" alt="Diagrama que muestra el flujo de control en programas sincrónicos y asíncronos. La primera parte muestra un programa sincrónico, donde las fases activas y de espera del programa ocurren en una única línea secuencial. La segunda parte muestra un programa sincrónico multi-hilo, con dos líneas paralelas en las cuales las partes de espera suceden una al lado de la otra, haciendo que el programa termine más rápido. La última parte muestra un programa asíncrono, donde las múltiples acciones asíncronas se ramifican desde el programa principal, el cual se detiene en algún momento y luego continúa cuando la primera cosa por la que estaba esperando finaliza."></figure>
<p><a class="p_ident" id="p-m0bMwsi7Hj" href="#p-m0bMwsi7Hj" tabindex="-1" role="presentation"></a>Otra forma de describir la diferencia es que esperar a que las acciones terminen es <em>implícito</em> en el modelo sincrónico, mientras que es <em>explícito</em>, bajo nuestro control, en el modelo asíncrono.</p>
<p><a class="p_ident" id="p-Gt0CDXTJDZ" href="#p-Gt0CDXTJDZ" tabindex="-1" role="presentation"></a>La asincronía tiene sus pros y sus contras. Facilita la expresión de programas que no encajan en el modelo de control en línea recta, pero también puede hacer que expresar programas que siguen una línea recta sea más complicado. Veremos algunas formas de reducir esta dificultad más adelante en el capítulo.</p>
<p><a class="p_ident" id="p-QDJln/phk5" href="#p-QDJln/phk5" tabindex="-1" role="presentation"></a>Las dos plataformas de programación de JavaScript más importantes —navegadores y Node.js— hacen que las operaciones que podrían tardar un tiempo sean asíncronas, en lugar de depender de hilos. Dado que programar con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno.</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-F908df1kGc" href="#p-F908df1kGc" tabindex="-1" role="presentation"></a>Un enfoque para la programación asíncrona es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una <em>función de devolución de llamada</em>, o <em>función de callback</em>. La función asíncrona inicia algún proceso, configura las cosas para que se llame a la función de callback cuando el proceso termine, y luego retorna.</p>
<p><a class="p_ident" id="p-D6ZI0eQstT" href="#p-D6ZI0eQstT" tabindex="-1" role="presentation"></a>Como ejemplo, la función <code>setTimeout</code>, disponible tanto en Node.js como en los navegadores, espera un número dado de milisegundos (un segundo equivale a mil milisegundos) y luego llama a una función.</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-hLrWLhn03U" href="#p-hLrWLhn03U" tabindex="-1" role="presentation"></a>Esperar no suele ser una tarea muy importante, pero puede ser muy útil cuando necesitas hacer que algo suceda en un momento determinado o verificar si alguna otra acción está tomando más tiempo del esperado.</p>
<p><a class="p_ident" id="p-wyrH3ZpP9/" href="#p-wyrH3ZpP9/" tabindex="-1" role="presentation"></a>Otro ejemplo de operación asíncrona común es leer un archivo desde el almacenamiento de un dispositivo. Imagina que tienes una función <code>readTextFile</code>, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de callback.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Zg8BmUPy+9" href="#c-Zg8BmUPy+9" tabindex="-1" role="presentation"></a>readTextFile(<span class="tok-string">"lista_compra.txt"</span>, <span class="tok-definition">contenido</span> => {
console.log(<span class="tok-string2">`Lista de Compras:\n</span>${contenido}<span class="tok-string2">`</span>);
});
<span class="tok-comment">// → Lista de Compras:</span>
<span class="tok-comment">// → Mantequilla de cacahuate</span>
<span class="tok-comment">// → Plátanos</span></pre>
<p><a class="p_ident" id="p-+9d/1JmuUd" href="#p-+9d/1JmuUd" tabindex="-1" role="presentation"></a>La función <code>readTextFile</code> no es parte del estándar de JavaScript. Veremos cómo leer archivos en el navegador y en Node.js en capítulos posteriores.</p>
<p><a class="p_ident" id="p-YF9vn5sToC" href="#p-YF9vn5sToC" tabindex="-1" role="presentation"></a>Realizar múltiples acciones asíncronas en serie usando callbacks implica que debes seguir pasando nuevas funciones para gestionar la continuación del proceso después de cada acción. Esta es la pinta que tendría una función asíncrona que compara dos archivos y produce un booleano que indica si su contenido es el mismo.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xRsv3/rEga" href="#c-xRsv3/rEga" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">compararArchivos</span>(<span class="tok-definition">archivoA</span>, <span class="tok-definition">archivoB</span>, <span class="tok-definition">callback</span>) {
readTextFile(archivoA, <span class="tok-definition">contenidoA</span> => {
readTextFile(archivoB, <span class="tok-definition">contenidoB</span> => {
callback(contenidoA == contenidoB);
});
});
}</pre>
<p><a class="p_ident" id="p-hw6hqXhrRg" href="#p-hw6hqXhrRg" tabindex="-1" role="presentation"></a>Este estilo de programación es factible, pero el nivel de sangrado aumenta con cada acción asíncrona porque terminas estando en otra función. Cosas más complicadas, como envolver acciones asíncronas en un bucle, pueden volverse muy incómodas.</p>
<p><a class="p_ident" id="p-E60GIBKpBQ" href="#p-E60GIBKpBQ" tabindex="-1" role="presentation"></a>De alguna manera, la asincronía es contagiosa. Cualquier función que llame a una función que trabaja de forma asíncrona debe ser asíncrona en sí misma, utilizando un callback u otro mecanismo similar para entregar su resultado. Llamar a una función callback es algo más complicado y propenso a errores que simplemente devolver un valor, por lo que crear la necesidad de estructurar grandes partes de tu programa de esa manera no es ideal.</p>
<h2><a class="h_ident" id="h-O5NEmIaSuD" href="#h-O5NEmIaSuD" tabindex="-1" role="presentation"></a>Promesas</h2>
<p><a class="p_ident" id="p-yfagraLtYh" href="#p-yfagraLtYh" tabindex="-1" role="presentation"></a>Una forma ligeramente diferente de construir un programa asíncrono es hacer que las funciones asíncronas devuelvan un objeto que represente su resultado (futuro) en lugar de pasar callbacks por todas partes. De esta manera, tales funciones realmente devuelven algo con sentido, y la estructura del programa se asemeja más a la de los programas sincrónicos.</p>
<p><a class="p_ident" id="p-XD820mj0vG" href="#p-XD820mj0vG" tabindex="-1" role="presentation"></a>Para esto sirve la clase estándar <code>Promise</code>. Una <em>promesa</em> es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método <code>then</code> que te permite registrar una función que debe ser llamada cuando la acción por la que está esperando finalice. Cuando la promesa se <em>resuelve</em>, es decir, cuando su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a <code>then</code> en una promesa que ya ha sido resuelta —tu función aún será llamada.</p>
<p><a class="p_ident" id="p-RGlDUtOJ93" href="#p-RGlDUtOJ93" tabindex="-1" role="presentation"></a>La forma más sencilla de crear una promesa es llamando a <code>Promise.resolve</code>. Esta función se asegura de que el valor que le proporcionas esté envuelto en una promesa. Si ya es una promesa, simplemente se devuelve; de lo contrario, obtienes una nueva promesa que se resuelve de inmediato con tu valor como resultado.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PQuvdLBL68" href="#c-PQuvdLBL68" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">quince</span> = Promise.resolve(<span class="tok-number">15</span>);
quince.then(<span class="tok-definition">valor</span> => console.log(<span class="tok-string2">`Obtenido </span>${valor}<span class="tok-string2">`</span>));
<span class="tok-comment">// → Obtenido 15</span></pre>
<p><a class="p_ident" id="p-c4wtF3+bV/" href="#p-c4wtF3+bV/" tabindex="-1" role="presentation"></a>Para crear una promesa que no se resuelva inmediatamente, puedes utilizar <code>Promise</code> como constructor. Tiene una interfaz un tanto extraña: el constructor espera una función como argumento, a la cual llama inmediatamente, pasándole como argumento una función (<code>resolver</code>, en el ejemplo) que puede utilizar para resolver la promesa.</p>
<p><a class="p_ident" id="p-C4xSB0FUME" href="#p-C4xSB0FUME" tabindex="-1" role="presentation"></a>Así es como podrías crear una interfaz basada en promesas para la función <code>readTextFile</code>:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RNQP/MOsHy" href="#c-RNQP/MOsHy" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoTexto</span>(<span class="tok-definition">nombreArchivo</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">resolver</span> => {
readTextFile(nombreArchivo, <span class="tok-definition">texto</span> => resolver(texto));
});
}
archivoTexto(<span class="tok-string">"planes.txt"</span>).then(console.log);</pre>
<p><a class="p_ident" id="p-sr4id/GYVf" href="#p-sr4id/GYVf" tabindex="-1" role="presentation"></a>Observa cómo esta función asíncrona devuelve un valor con sentido: una promesa de proporcionarte el contenido del archivo en algún momento futuro.</p>
<p><a class="p_ident" id="p-JzjtMWC7B5" href="#p-JzjtMWC7B5" tabindex="-1" role="presentation"></a>Una característica útil del método <code>then</code> es que él mismo devuelve otra promesa que se resuelve al valor retornado por la función de callback o, si esa función devuelve una promesa, al valor al que esa promesa se resuelve. De esta forma, puedes “encadenar” varias llamadas a <code>then</code> para configurar una secuencia de acciones asíncronas.</p>
<p><a class="p_ident" id="p-kO8eylKtGY" href="#p-kO8eylKtGY" tabindex="-1" role="presentation"></a>Esta función, la cual lee un archivo lleno de nombres de archivos y devuelve el contenido de un archivo aleatorio de esa lista, muestra este tipo de cadena asíncrona de promesas.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tgcWPWseIi" href="#c-tgcWPWseIi" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoAleatorio</span>(<span class="tok-definition">archivoLista</span>) {
<span class="tok-keyword">return</span> archivoTexto(archivoLista)
.then(<span class="tok-definition">contenido</span> => contenido.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">nombreArchivo</span> => archivoTexto(nombreArchivo));
}</pre>
<p><a class="p_ident" id="p-vwGbLsp+ut" href="#p-vwGbLsp+ut" tabindex="-1" role="presentation"></a>La función devuelve el resultado de esta cadena de llamadas a <code>then</code>. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a <code>then</code> transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a <code>then</code> elige una línea aleatoria del resultado de resolver esta promesa, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a <code>then</code> lee este archivo, de modo que el resultado de la función en su totalidad es una promesa que devuelve el contenido de un archivo aleatorio.</p>
<p><a class="p_ident" id="p-RfQgfieqda" href="#p-RfQgfieqda" tabindex="-1" role="presentation"></a>En este código, las funciones utilizadas en las primeras dos llamadas a <code>then</code> devuelven un valor normal, que se pasará inmediatamente a la promesa devuelta por <code>then</code> cuando la función retorne. La última devuelve una promesa (<code>archivoTexto(nombreArchivo)</code>), lo que la convierte en un paso asíncrono de verdad.</p>
<p><a class="p_ident" id="p-z9ZsQuS8Cj" href="#p-z9ZsQuS8Cj" tabindex="-1" role="presentation"></a>También habría sido posible realizar todos estos pasos dentro de un solo callback de <code>then</code>, ya que solo el último paso es realmente asíncrono. Pero el tipo de envolturas <code>then</code> que solo realizan alguna transformación de datos sincrónica son a menudo útiles, por ejemplo, cuando deseas devolver una promesa que produzca una versión procesada de algún resultado asíncrono.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BcfrCnNsnL" href="#c-BcfrCnNsnL" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoJson</span>(<span class="tok-definition">nombreArchivo</span>) {
<span class="tok-keyword">return</span> archivoTexto(nombreArchivo).then(JSON.parse);
}
archivoJson(<span class="tok-string">"package.json"</span>).then(console.log);</pre>
<p><a class="p_ident" id="p-YrcahdlIgs" href="#p-YrcahdlIgs" tabindex="-1" role="presentation"></a>En general, es útil pensar en las promesas como un mecanismo que permite al código ignorar la pregunta de cuándo va a llegar un valor. Un valor normal tiene que existir realmente antes de que podamos hacer referencia a él. Un valor prometido es un valor que <em>puede</em> estar allí o podría aparecer en algún momento en el futuro. Las operaciones definidas en términos de promesas, al conectarlas con llamadas <code>then</code>, se ejecutan de forma asíncrona a medida que sus entradas están disponibles.</p>
<h2><a class="h_ident" id="h-M4lb98nplJ" href="#h-M4lb98nplJ" tabindex="-1" role="presentation"></a>Fallo</h2>
<p><a class="p_ident" id="p-szOVDrRnwN" href="#p-szOVDrRnwN" tabindex="-1" role="presentation"></a>Un procedimiento normal de JavaScript puede fallar lanzando una excepción. Los procedimientos asíncronos a menudo necesitan algo así. Una solicitud de red puede fallar, un archivo puede no existir, o algún código que forma parte de un procedimiento asíncrono puede lanzar una excepción.</p>
<p><a class="p_ident" id="p-YIfhZsaqoF" href="#p-YIfhZsaqoF" tabindex="-1" role="presentation"></a>Uno de los problemas más urgentes del estilo de programación asíncrona basado en callbacks es que hace extremadamente difícil asegurarse de que los fallos se reporten adecuadamente a las funciones de callback.</p>
<p><a class="p_ident" id="p-nEBtfDSRB+" href="#p-nEBtfDSRB+" tabindex="-1" role="presentation"></a>Una convención ampliamente utilizada es que el primer argumento de la función de callback se utiliza para indicar que la acción ha fallado, y el segundo contiene el valor producido por la acción cuando ha terminado con éxito.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/lUgiJQG9L" href="#c-/lUgiJQG9L" tabindex="-1" role="presentation"></a>unaFuncionAsincrona((<span class="tok-definition">error</span>, <span class="tok-definition">valor</span>) => {
<span class="tok-keyword">if</span> (error) manejarError(error);
<span class="tok-keyword">else</span> procesarValor(valor);
});</pre>
<p><a class="p_ident" id="p-WOsDKk7EQx" href="#p-WOsDKk7EQx" tabindex="-1" role="presentation"></a>Tales funciones de callback siempre deben verificar si recibieron una excepción y asegurarse de que cualquier problema que causen, incluidas las excepciones lanzadas por las funciones que llaman, se capturen y se den a la función correcta.</p>
<p><a class="p_ident" id="p-xKl/89KNJb" href="#p-xKl/89KNJb" tabindex="-1" role="presentation"></a>Las promesas facilitan esto. Pueden ser o bien resueltas (la acción se completó con éxito) o rechazadas (la acción falló). Los manejadores de resolución (registrados con <code>then</code>) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa devuelta por <code>then</code>. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por su llamada a <code>then</code> sea rechazada. Entonces, si algún elemento en una cadena de acciones asíncronas falla, el resultado de toda la cadena se marca como rechazado, y ningún manejador de éxito se ejecuta más allá del punto en el que ocurrió el fallo.</p>
<p><a class="p_ident" id="p-JZgXPDvBUG" href="#p-JZgXPDvBUG" tabindex="-1" role="presentation"></a>Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el <em>motivo</em> del rechazo. Cuando una excepción en una función manejadora causa el rechazo, el valor de la excepción se usa como dicho motivo. De manera similar, cuando una función manejadora devuelve una promesa que es rechazada, ese rechazo fluye hacia la siguiente promesa. Existe una función <code>Promise.reject</code> que crea una nueva promesa inmediatamente rechazada.</p>
<p><a class="p_ident" id="p-gAl8dny3ZI" href="#p-gAl8dny3ZI" tabindex="-1" role="presentation"></a>Para manejar explícitamente tales rechazos, las promesas tienen un método <code>catch</code> que registra un manejador para ser llamado cuando la promesa es rechazada, similar a cómo los manejadores de <code>then</code> manejan la resolución normal. También es muy similar a <code>then</code> en que devuelve una nueva promesa, que se resuelve con el valor de la promesa original cuando se resuelve normalmente y con el resultado del manejador <code>catch</code> en caso contrario. Si un manejador de <code>catch</code> lanza un error, la nueva promesa también se rechaza.</p>
<p><a class="p_ident" id="p-YEPo8wATzV" href="#p-YEPo8wATzV" tabindex="-1" role="presentation"></a>Como atajo, <code>then</code> también acepta un manejador de rechazo como segundo argumento, conque puedes instalar ambos tipos de manejadores en una sola llamada de método: <code>.<wbr>then(manejadorDeAceptación, manejadorDeRechazo)</code>.</p>
<p><a class="p_ident" id="p-cAXjTS7KlW" href="#p-cAXjTS7KlW" tabindex="-1" role="presentation"></a>Una función pasada al constructor <code>Promise</code> recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa. Cuando nuestra función <code>readTextFile</code> encuentra un problema, pasa el error a su función callback como segundo argumento. Nuestro envoltorio <code>archivoTexto</code> debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BXbh8xpAiG" href="#c-BXbh8xpAiG" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoTexto</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-LtILeb1bZh" href="#p-LtILeb1bZh" tabindex="-1" role="presentation"></a>Las cadenas de valores de promesa creadas por llamadas a <code>then</code> y <code>catch</code> forman así un pipeline a través del cual se mueven los valores asíncronos o fallos. Dado que dichas cadenas se crean registrando manejadores, cada eslabón tiene asociado un manejador de éxito o un manejador de rechazo (o ambos). Los manejadores que no coinciden con el tipo de resultado (éxito o fallo) son ignorados. Pero aquellos que coinciden son llamados, y su resultado determina qué tipo de valor viene a continuación: éxito cuando devuelve un valor que no es una promesa, rechazo cuando genera una excepción, y el resultado de la promesa cuando devuelve una promesa.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-7aeG1gDVR8" href="#c-7aeG1gDVR8" tabindex="-1" role="presentation"></a><span class="tok-keyword">new</span> Promise((<span class="tok-definition">_</span>, <span class="tok-definition">rechazar</span>) => rechazar(<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">"Manejador 1:"</span>, value))
.catch(<span class="tok-definition">reason</span> => {
console.log(<span class="tok-string">"Error capturado "</span> + reason);
<span class="tok-keyword">return</span> <span class="tok-string">"nada"</span>;
})
.then(<span class="tok-definition">value</span> => console.log(<span class="tok-string">"Manejador 2:"</span>, value));
<span class="tok-comment">// → Error capturado Error: Fail</span>
<span class="tok-comment">// → Manejador 2: nada</span></pre>
<div class="translator-note"><p><strong>N. del T.:</strong> nótese cómo el parámetro que se pasa al constructor <code>Promise</code> es una función con dos parámetros que no representan otra cosa que el nombre de las funciones de resolución y rechazo que espera el constructor. JavaScript ya sabe que la función cuyo nombre se pasa como primer parámetro hará lo que se necesite cuando la promesa se resuelve sin problemas, y que la función cuyo nombre se pasa como segundo parámetro hará lo propio cuando la promesa es rechazada. El nombre que les pongamos a dichos parámetros es indiferente, aunque suele usarse <code>resolve</code> para el primer caso y <code>reject</code> para el segundo o, como en este ejemplo, <code>_</code> para el primero (porque ni siquiera lo necesitamos) y <code>rechazar</code> para el segundo.</p>
</div>
<p><a class="p_ident" id="p-IbdqRNj4zc" href="#p-IbdqRNj4zc" tabindex="-1" role="presentation"></a>El primer manejador <code>then</code> no es llamado porque, en ese punto del pipeline, la promesa contiene un rechazo. El manejador <code>catch</code> maneja ese rechazo y devuelve un valor, que se le da al segundo manejador <code>then</code>.</p>
<p><a class="p_ident" id="p-nAXpd8J86T" href="#p-nAXpd8J86T" tabindex="-1" role="presentation"></a>Cuando una excepción no controlada es manejada por el entorno, los entornos de JavaScript pueden detectar cuándo un rechazo de promesa no es manejado y lo reportarán como un error.</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-sI2r0mTENb" href="#p-sI2r0mTENb" tabindex="-1" role="presentation"></a>Es un día soleado en Berlín. La pista del antiguo aeropuerto desmantelado está llena de ciclistas y patinadores en línea. En el césped, cerca de un contenedor de basura, un grupo de cuervos se agita ruidosamente, intentando convencer a un grupo de turistas de que les den sus sándwiches.</p>
<p><a class="p_ident" id="p-jIHoxx51eu" href="#p-jIHoxx51eu" tabindex="-1" role="presentation"></a>Uno de los cuervos destaca: una hembra grande, andrajosa, con algunas plumas blancas en su ala derecha. Está atrayendo a la gente con una habilidad y confianza que sugieren que ha estado haciendo esto durante mucho tiempo. Cuando un anciano se distrae con las travesuras de otro cuervo, ella se abalanza como quien no quiere la cosa, le arrebata su bollo a medio comer de la mano y se aleja planeando.</p>
<p><a class="p_ident" id="p-cvIv/A82B2" href="#p-cvIv/A82B2" tabindex="-1" role="presentation"></a>A diferencia del resto del grupo, que parece estar feliz de pasar el día holgazaneando por ahí, el cuervo grande parece tener un propósito. Llevando su botín, vuela directamente hacia el techo del edificio del hangar, desapareciendo por un conducto de ventilación.</p>
<p><a class="p_ident" id="p-Msy2NLY8h2" href="#p-Msy2NLY8h2" tabindex="-1" role="presentation"></a>Dentro del edificio, se puede escuchar un sonido peculiar: suave, pero persistente. Viene de un espacio estrecho bajo el techo de una escalera sin terminar. El cuervo está sentado allí, rodeado de sus botines robados: media docena de teléfonos inteligentes (varios de los cuales están encendidos) y un enredo de cables. Golpea rápidamente la pantalla de uno de los teléfonos con su pico. Aparecen palabras en él. Si no supieras más, pensarías que estaba escribiendo.</p>
<p><a class="p_ident" id="p-xilnVxK0xf" href="#p-xilnVxK0xf" tabindex="-1" role="presentation"></a>Este cuervo es conocido por sus iguales como “cāāw-krö". Pero dado que esos sonidos no son adecuados para las cuerdas vocales humanas, la llamaremos Carla.</p>
<p><a class="p_ident" id="p-qyFUB1V+JF" href="#p-qyFUB1V+JF" tabindex="-1" role="presentation"></a>Carla es un cuervo algo peculiar. En su juventud, estaba fascinada por el lenguaje humano, escuchando a la gente hasta que llegó incluso a entender lo que decían. Más tarde, su interés se trasladó a la tecnología humana, y comenzó a robar teléfonos para estudiarlos. Su proyecto actual es aprender a programar. El texto que está escribiendo en su laboratorio secreto, de hecho, es un fragmento de código JavaScript.</p>
<h2><a class="h_ident" id="h-/5q4Gr2m3m" href="#h-/5q4Gr2m3m" tabindex="-1" role="presentation"></a>Infiltración</h2>
<p><a class="p_ident" id="p-lIH7NawKaH" href="#p-lIH7NawKaH" tabindex="-1" role="presentation"></a>A Carla le encanta Internet. Por desgracia, el teléfono en el que está trabajando está a punto de quedarse sin datos. El edificio tiene una red inalámbrica, pero se requiere un código para acceder a ella.</p>
<p><a class="p_ident" id="p-bzZjZThivN" href="#p-bzZjZThivN" tabindex="-1" role="presentation"></a>Afortunadamente, los rúteres inalámbricos del edificio tienen 20 años y están mal protegidos. Tras investigar un poco, Carla descubre que el mecanismo de autenticación de la red tiene un fallo que puede aprovechar. Al unirse a la red, un dispositivo debe enviar el código correcto de 6 dígitos. El punto de acceso responderá con un mensaje de éxito o fracaso dependiendo de si se proporciona el código correcto. Sin embargo, al enviar solo un código parcial (digamos, solo 3 dígitos), la respuesta es diferente según si esos dígitos son el inicio correcto del código o no. Cuando se envía un número incorrecto, se recibe inmediatamente un mensaje de fracaso. Cuando se envían los dígitos correctos, el punto de acceso espera más dígitos.</p>
<p><a class="p_ident" id="p-oe48QPvm/G" href="#p-oe48QPvm/G" tabindex="-1" role="presentation"></a>Esto acelera enormemente el descubrimiento del número. Carla puede encontrar el primer dígito probando cada número uno a uno, hasta que encuentre uno que no devuelva inmediatamente un fracaso. Teniendo un dígito, puede encontrar el segundo de la misma manera, y así sucesivamente, hasta que conozca todo el código de acceso.</p>
<p><a class="p_ident" id="p-RIZ6rVDtx9" href="#p-RIZ6rVDtx9" tabindex="-1" role="presentation"></a>Supongamos que tenemos una función <code>joinWifi</code>. Dado el nombre de la red y el código de acceso (como una cadena), intenta unirse a la red, devolviendo una promesa que se resuelve si tiene éxito, y se rechaza si la autenticación falla. Lo primero que necesitamos es una forma de envolver una promesa para que se rechace automáticamente después de transcurrir demasiado tiempo, de manera que podamos avanzar rápidamente si el punto de acceso no responde.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-m5DqK0OfBr" href="#c-m5DqK0OfBr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">conTiempoDeEspera</span>(<span class="tok-definition">promesa</span>, <span class="tok-definition">tiempo</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolver</span>, <span class="tok-definition">rechazar</span>) => {
promesa.then(resolver, rechazar);
setTimeout(() => rechazar(<span class="tok-string">"Se agotó el tiempo"</span>), tiempo);
});
}</pre>
<p><a class="p_ident" id="p-851dYoRdgk" href="#p-851dYoRdgk" tabindex="-1" role="presentation"></a>Esto aprovecha el hecho de que una promesa solo puede resolverse o rechazarse una vez: si la promesa dada como argumento se resuelve o se rechaza primero, ese será el resultado de la promesa devuelta por <code>conTiempoDeEspera</code>. Si, por otro lado, el <code>setTimeout</code> se ejecuta primero, rechazando la promesa, se ignora cualquier llamada posterior de resolución o rechazo.</p>
<p><a class="p_ident" id="p-aGXBBNJk0l" href="#p-aGXBBNJk0l" tabindex="-1" role="presentation"></a>Para encontrar todo el código de acceso, necesitamos buscar repetidamente el siguiente dígito probando cada dígito. Si la autenticación tiene éxito, sabremos que hemos encontrado lo que buscamos. Si falla inmediatamente, sabremos que ese dígito era incorrecto y debemos probar con el siguiente. Si el tiempo de la solicitud se agota, hemos encontrado otro dígito correcto y debemos continuar agregando otro dígito.</p>
<p><a class="p_ident" id="p-H9/JmKGCa9" href="#p-H9/JmKGCa9" tabindex="-1" role="presentation"></a>Como no puedes esperar una promesa dentro de un bucle <code>for</code>, Carla utiliza una función recursiva para llevar a cabo este proceso. En cada llamada, obtiene el código tal como lo conocemos hasta ahora, así como el siguiente dígito a probar. Dependiendo de lo que suceda, puede devolver un código terminado, o llamarse de nuevo a sí misma, ya sea para comenzar a descifrar la siguiente posición en el código, o para intentarlo de nuevo con otro dígito.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rS6USRJVfv" href="#c-rS6USRJVfv" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">crackearContraseña</span>(<span class="tok-definition">identificadorDeRed</span>) {
<span class="tok-keyword">function</span> <span class="tok-definition">siguienteDígito</span>(<span class="tok-definition">código</span>, <span class="tok-definition">dígito</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">nuevoCódigo</span> = código + dígito;
<span class="tok-keyword">return</span> conTiempoDeEspera(joinWifi(identificadorDeRed, nuevoCódigo), <span class="tok-number">50</span>)
.then(() => nuevoCódigo)
.catch(<span class="tok-definition">fallo</span> => {
<span class="tok-keyword">if</span> (fallo == <span class="tok-string">"Se agotó el tiempo"</span>) {
<span class="tok-keyword">return</span> siguienteDígito(nuevoCódigo, <span class="tok-number">0</span>);
} <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (dígito < <span class="tok-number">9</span>) {
<span class="tok-keyword">return</span> siguienteDígito(código, dígito + <span class="tok-number">1</span>);
} <span class="tok-keyword">else</span> {
<span class="tok-keyword">throw</span> fallo;
}
});
}
<span class="tok-keyword">return</span> siguienteDígito(<span class="tok-string">""</span>, <span class="tok-number">0</span>);
}</pre>
<p><a class="p_ident" id="p-7c/57tY3Ft" href="#p-7c/57tY3Ft" tabindex="-1" role="presentation"></a>El punto de acceso suele responder a solicitudes de autenticación incorrectas en aproximadamente 20 milisegundos, por lo que, para estar seguros, esta función espera 50 milisegundos antes de hacer expirar una solicitud.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-LaAB7vFIXo" href="#c-LaAB7vFIXo" tabindex="-1" role="presentation"></a>crackearContraseña(<span class="tok-string">"HANGAR 2"</span>).then(console.log);
<span class="tok-comment">// → 555555</span></pre>
<p><a class="p_ident" id="p-KpmbzaGvuk" href="#p-KpmbzaGvuk" tabindex="-1" role="presentation"></a>Carla inclina la cabeza y suspira. Esto habría sido más satisfactorio si el código hubiera sido un poco más difícil de adivinar.</p>
<h2><a class="h_ident" id="h-NGTlK4NQNX" href="#h-NGTlK4NQNX" tabindex="-1" role="presentation"></a>Funciones asíncronas</h2>
<p><a class="p_ident" id="p-FbcOLp9fV4" href="#p-FbcOLp9fV4" tabindex="-1" role="presentation"></a>Incluso con promesas, este tipo de código asíncrono es molesto de escribir. A menudo, necesitamos encadenar promesas de manera verbosa y de aparencia arbitraria —Carla ha tenido que usar una función recursiva para crear un bucle asíncrono.</p>
<p><a class="p_ident" id="p-GZ6dLvPHxn" href="#p-GZ6dLvPHxn" tabindex="-1" role="presentation"></a>Lo que la función <code>crackearContraseña</code> realmente hace es completamente lineal: siempre espera a que la acción anterior se complete antes de comenzar la siguiente. Sería más sencillo de expresar en un modelo de programación sincrónica.</p>
<p><a class="p_ident" id="p-G5SArOKa1V" href="#p-G5SArOKa1V" tabindex="-1" role="presentation"></a>La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir procedimientos asíncronos. Una función <code>async</code> es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, esperar (<code>await</code>) otras promesas de una manera que <em>parece</em> sincrónica.</p>
<p><a class="p_ident" id="p-QE3XBITstL" href="#p-QE3XBITstL" tabindex="-1" role="presentation"></a>Podemos reescribir <code>crackearContraseña</code> de la siguiente manera:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WVlzd+NWdu" href="#c-WVlzd+NWdu" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">crackearContraseña</span>(<span class="tok-definition">identificadorDeRed</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">código</span> = <span class="tok-string">""</span>;;) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dígito</span> = <span class="tok-number">0</span>;; dígito++) {
<span class="tok-keyword">let</span> <span class="tok-definition">nuevoCódigo</span> = código + dígito;
<span class="tok-keyword">try</span> {
<span class="tok-keyword">await</span> withTimeout(joinWifi(identificadorDeRed, nuevoCódigo), <span class="tok-number">50</span>);
<span class="tok-keyword">return</span> nuevoCódigo;
} <span class="tok-keyword">catch</span> (<span class="tok-definition">fallo</span>) {
<span class="tok-keyword">if</span> (fallo == <span class="tok-string">"Se agotó el tiempo"</span>) {
código = nuevoCódigo;
<span class="tok-keyword">break</span>;
} <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (dígito == <span class="tok-number">9</span>) {
<span class="tok-keyword">throw</span> fallo;
}
}
}
}
}</pre>
<p><a class="p_ident" id="p-+4mFixoWg7" href="#p-+4mFixoWg7" tabindex="-1" role="presentation"></a>Esta versión muestra de manera más clara la estructura de doble bucle de la función (el bucle interno prueba los dígitos del 0 al 9 y el bucle externo añade dígitos al código de acceso).</p>
<p><a class="p_ident" id="p-MHiEBPHcq0" href="#p-MHiEBPHcq0" tabindex="-1" role="presentation"></a>Una función <code>async</code> está marcada con la palabra <code>async</code> antes de la palabra clave <code>function</code>. Los métodos también se pueden marcar como <code>async</code> escribiendo <code>async</code> antes de su nombre. Cuando se llama a una función o método de esta manera, lo que se devuelve es una promesa. Tan pronto como la función devuelve algo, esa promesa se resuelve. Si el cuerpo genera una excepción, la promesa es rechazada.</p>
<p><a class="p_ident" id="p-GXfnmGh9zs" href="#p-GXfnmGh9zs" tabindex="-1" role="presentation"></a>Dentro de una función <code>async</code>, la palabra <code>await</code> puede colocarse delante de una expresión para esperar a que una promesa se resuelva y luego continuar con la ejecución de la función. Si la promesa es rechazada, se genera una excepción en el punto del <code>await</code>.</p>
<p><a class="p_ident" id="p-o9TSZxwXki" href="#p-o9TSZxwXki" tabindex="-1" role="presentation"></a>Una función de estas ya no se ejecuta de principio a fin de una vez como una función normal de JavaScript. En su lugar, puede estar <em>congelada</em> en cualquier punto que tenga un <code>await</code>, y continuar más tarde.</p>
<p><a class="p_ident" id="p-mZrEY5AcpL" href="#p-mZrEY5AcpL" tabindex="-1" role="presentation"></a>Para la mayoría del código asíncrono, esta notación es más conveniente que usar directamente promesas. Aún así, es necesario comprender las promesas, ya que en muchos casos interactuarás con ellas directamente de todos modos. Pero al encadenarlas, las funciones <code>async</code> suelen ser más agradables de escribir que encadenar llamadas a <code>then</code>.</p>
<h2 id="generator"><a class="h_ident" id="h-LGLwBM7N7Q" href="#h-LGLwBM7N7Q" tabindex="-1" role="presentation"></a>Generadores</h2>
<p><a class="p_ident" id="p-vucanJZjN2" href="#p-vucanJZjN2" tabindex="-1" role="presentation"></a>Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones <code>async</code>. JavaScript también tiene una característica llamada funciones generadoras (<em>generator functions</em>). Estas son parecidas a las funciones <code>async</code>, pero sin las promesas.</p>
<p><a class="p_ident" id="p-KhGB+fXocF" href="#p-KhGB+fXocF" tabindex="-1" role="presentation"></a>Cuando defines una función con <code>function*</code> (colocando un asterisco después de la palabra <code>function</code>), se convierte en un generador. Al llamar a un generador, este devuelve un iterador, que ya estudiamos en el <a href="06_object.html">Capítulo 6</a>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-+/YWo6RHLX" href="#c-+/YWo6RHLX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span><span class="tok-keyword">*</span> <span class="tok-definition">potencias</span>(<span class="tok-definition">n</span>) {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">actual</span> = n;; actual *= n) {
<span class="tok-keyword">yield</span> actual;
}
}
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">potencia</span> <span class="tok-keyword">of</span> potencias(<span class="tok-number">3</span>)) {
<span class="tok-keyword">if</span> (potencia > <span class="tok-number">50</span>) <span class="tok-keyword">break</span>;
console.log(potencia);
}
<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-aD9V5e562+" href="#p-aD9V5e562+" tabindex="-1" role="presentation"></a>Inicialmente, al llamar a <code>potencias</code>, la función se congela desde el principio. Cada vez que llamas a <code>next</code> en el iterador, la función se ejecuta hasta que encuentra una expresión <code>yield</code>, que la pausa y hace que el valor generado se convierta en el próximo valor producido por el iterador. Cuando la función retorna (la del ejemplo nunca lo hace), el iterador ha terminado.</p>
<p><a class="p_ident" id="p-+edzRQNWn2" href="#p-+edzRQNWn2" tabindex="-1" role="presentation"></a>Escribir iteradores a menudo es mucho más fácil cuando usas funciones generadoras. El iterador para la clase <code>Group</code> (del ejercicio en el <a href="06_object.html#group_iterator">Capítulo 6</a>) se puede escribir con este generador:</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-Tcmh5kj782" href="#p-Tcmh5kj782" tabindex="-1" role="presentation"></a>Ya no es necesario crear un objeto para mantener el estado de la iteración: los generadores guardan automáticamente su estado local cada vez que hacen un <code>yield</code>.</p>
<p><a class="p_ident" id="p-C276cu76si" href="#p-C276cu76si" tabindex="-1" role="presentation"></a>Tales expresiones <code>yield</code> solo pueden ocurrir directamente en la función generadora misma y no en una función interna que definas dentro de ella. El estado que un generador guarda, al hacer yield, es solo su entorno <em>local</em> y la posición donde hizo el yield.</p>
<p><a class="p_ident" id="p-+kfgMm93qQ" href="#p-+kfgMm93qQ" tabindex="-1" role="presentation"></a>Una función <code>async</code> es un tipo especial de generador. Produce una promesa al llamarla, la cual se resuelve cuando retorna (termina) y se rechaza cuando arroja una excepción. Cada vez que hace un yield de una promesa (es decir, la espera con <code>await</code>), el resultado de esa promesa (el valor o la excepción generada) es el resultado de la expresión <code>await</code>.</p>
<h2><a class="h_ident" id="h-8PK3DJlJpe" href="#h-8PK3DJlJpe" tabindex="-1" role="presentation"></a>Un Proyecto de Arte de Córvidos</h2>
<p><a class="p_ident" id="p-EIAKJkXc8j" href="#p-EIAKJkXc8j" tabindex="-1" role="presentation"></a>Esta mañana, Carla se despertó con un ruido desconocido en la pista de aterrizaje fuera de su hangar. Saltando al borde del techo, ve que los humanos están preparando algo. Hay muchos cables eléctricos, un escenario y una especie de gran pared negra que están construyendo.</p>
<p><a class="p_ident" id="p-yOmEX58AMX" href="#p-yOmEX58AMX" tabindex="-1" role="presentation"></a>Como es una cuerva curiosa, Carla echa un vistazo más de cerca a la pared. Parece estar compuesta por varios dispositivos grandes con un frontal de vidrio conectados a cables. En la parte trasera, los dispositivos dicen “LedTec SIG-5030”.</p>
<p><a class="p_ident" id="p-yBNrkTbIgn" href="#p-yBNrkTbIgn" tabindex="-1" role="presentation"></a>Una rápida búsqueda en Internet saca a relucir un manual de usuario para estos dispositivos. Parecen ser señales de tráfico, con una matriz programable de luces LED de color ámbar. La intención de los humanos probablemente sea mostrar algún tipo de información en ellas durante su evento. Curiosamente, las pantallas pueden ser programadas a través de una red inalámbrica. ¿Será posible que estén conectadas a la red local del edificio?</p>
<p><a class="p_ident" id="p-4e69YlbBkR" href="#p-4e69YlbBkR" tabindex="-1" role="presentation"></a>Cada dispositivo en una red recibe una <em>dirección IP</em>, que otros dispositivos pueden usar para enviarle mensajes. Hablaremos más sobre eso en el <a href="13_browser.html">Capítulo 13</a>. Carla se da cuenta que sus propios teléfonos reciben direcciones como <code>10.0.0.20</code> o <code>10.0.0.33</code>. Podría valer la pena intentar enviar mensajes a todas esas direcciones y ver si alguna responde a la interfaz descrita en el manual de las señales.</p>
<p><a class="p_ident" id="p-nKIobazOIw" href="#p-nKIobazOIw" tabindex="-1" role="presentation"></a>El <a href="18_http.html">Capítulo 18</a> muestra cómo hacer solicitudes reales en redes reales. En este capítulo, usaremos una función ficticia simplificada llamada <code>request</code> para la comunicación en red. Esta función toma dos argumentos: una dirección de red y un mensaje, que puede ser cualquier cosa que se pueda enviar como JSON, y devuelve una promesa que se resuelve con una respuesta de la máquina en la dirección dada, o se rechaza si hubo un problema.</p>
<p><a class="p_ident" id="p-3j7VheCqjp" href="#p-3j7VheCqjp" tabindex="-1" role="presentation"></a>Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como <code>{"command": "display", "data": [0, 0, 3, …]}</code>, donde <code>data</code> contiene un número por cada LED, indicando su brillo; 0 significa apagado, 3 significa brillo máximo. Cada señal tiene 50 luces de ancho y 30 luces de alto, por lo que un comando de actualización debe enviar 1500 números.</p>
<p><a class="p_ident" id="p-Id2shPCIz0" href="#p-Id2shPCIz0" tabindex="-1" role="presentation"></a>Este código envía un mensaje de actualización de pantalla a todas las direcciones en la red local para ver cuál se queda. Cada uno de los números en una dirección IP puede ir de 0 a 255. En los datos que envía, activa un número de luces correspondiente al último número de la dirección de red.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6rc6x2fPE6" href="#c-6rc6x2fPE6" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = <span class="tok-number">1</span>; dir < <span class="tok-number">256</span>; dir++) {
<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 < dir ? <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>${dir}<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">`Solicitud a </span>${ip}<span class="tok-string2"> aceptada`</span>))
.catch(() => {});
}</pre>
<p><a class="p_ident" id="p-nJVIxRRyu8" href="#p-nJVIxRRyu8" tabindex="-1" role="presentation"></a>Dado que la mayoría de estas direcciones no existirán o no aceptarán tales mensajes, la llamada a <code>catch</code> se asegura de que los errores de red no hagan que el programa falle. Las solicitudes se envían todas inmediatamente, sin esperar a que otras solicitudes terminen, para no perder tiempo cuando algunas de las máquinas no respondan.</p>
<p><a class="p_ident" id="p-XT02BFndLm" href="#p-XT02BFndLm" tabindex="-1" role="presentation"></a>Después de haber iniciado su exploración de red, Carla regresa afuera para ver el resultado. Para su deleite, todas las pantallas ahora muestran una franja de luz en sus esquinas superiores izquierdas. Están en la red local y sí aceptan comandos. Rápidamente toma nota de los números mostrados en cada pantalla. Hay 9 pantallas, dispuestas tres en alto y tres en ancho. Tienen las siguientes direcciones de red:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-0xZYyiiOuj" href="#c-0xZYyiiOuj" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">direccionesPantalla</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-4xFvLyVJ47" href="#p-4xFvLyVJ47" tabindex="-1" role="presentation"></a>Ahora esto abre posibilidades para todo tipo de travesuras. Podría mostrar “los cuervos mandan, los humanos babean” en la pared en letras gigantes. Pero eso se parece un poco grosero. En su lugar, planea mostrar a la noche un vídeo de un cuervo volando que cubra todas las pantallas.</p>
<p><a class="p_ident" id="p-y5Q2eMHG15" href="#p-y5Q2eMHG15" tabindex="-1" role="presentation"></a>Carla encuentra un vídeo adecuado en el cual un segundo y medio de metraje se puede repetir para crear un vídeo en bucle mostrando el aleteo de un cuervo. Para ajustarse a las nueve pantallas (cada una de las cuales puede mostrar 50 por 30 píxeles), Carla corta y redimensiona los vídeos para obtener una serie de imágenes de 150 por 90, diez por segundo. Estas luego se cortan en nueve rectángulos cada una, y se procesan para que los puntos oscuros en el vídeo (donde está el cuervo) muestren una luz brillante, y los puntos claros (sin cuervo) permanezcan oscuros, lo que debería crear el efecto de un cuervo ámbar volando contra un fondo negro.</p>
<p><a class="p_ident" id="p-ikDdJwR2zo" href="#p-ikDdJwR2zo" tabindex="-1" role="presentation"></a>Ha configurado la variable <code>imágenesVídeo</code> para contener un array de fotogramas, donde cada fotograma se representa con un array de nueve conjuntos de píxeles, uno para cada pantalla, en el formato que los letreros esperan.</p>
<p><a class="p_ident" id="p-KvXTg6wDLF" href="#p-KvXTg6wDLF" tabindex="-1" role="presentation"></a>Para mostrar un único fotograma del vídeo, Carla necesita enviar una solicitud a todas las pantallas a la vez. Pero también necesita esperar el resultado de estas solicitudes, tanto para no comenzar a enviar el siguiente fotograma antes de que el actual se haya enviado correctamente, como para notar cuando las solicitudes están fallando.</p>
<p><a class="p_ident" id="p-YW17AqPsQ1" href="#p-YW17AqPsQ1" tabindex="-1" role="presentation"></a><code>Promise</code> tiene un método estático <code>all</code> que se puede usar para convertir un array de promesas en una sola promesa que se resuelve en un array de resultados. Esto proporciona una forma conveniente de que algunas acciones asíncronas sucedan de manera concurrente, esperar a que todas terminen y luego hacer algo con sus resultados (o al menos esperar a que terminen para asegurarse de que no fallen).</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-83G/xzS9mc" href="#c-83G/xzS9mc" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mostrarFotograma</span>(<span class="tok-definition">fotograma</span>) {
<span class="tok-keyword">return</span> Promise.all(fotograma.map((<span class="tok-definition">data</span>, <span class="tok-definition">i</span>) => {
<span class="tok-keyword">return</span> request(direccionesPantalla[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-FPLMeF12gv" href="#p-FPLMeF12gv" tabindex="-1" role="presentation"></a>Esto recorre las imágenes en <code>fotograma</code> (que es un array de arrays de datos de visualización) para crear un array de promesas de solicitud. Luego devuelve una promesa que combina todas esas promesas.</p>
<p><a class="p_ident" id="p-HlRvWcaLgh" href="#p-HlRvWcaLgh" tabindex="-1" role="presentation"></a>Para tener la capacidad de detener un vídeo en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono <code>reproducir</code> que devuelve una promesa que solo se resuelve cuando la reproducción se detiene a través del método <code>parar</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-jhZtnszLIr" href="#c-jhZtnszLIr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">espera</span>(<span class="tok-definition">tiempo</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">aceptar</span> => setTimeout(aceptar, tiempo));
}
<span class="tok-keyword">class</span> ReproductorVídeo {
<span class="tok-definition">constructor</span>(<span class="tok-definition">fotogramas</span>, <span class="tok-definition">tiempoFotograma</span>) {
<span class="tok-keyword">this</span>.fotogramas = fotogramas;
<span class="tok-keyword">this</span>.tiempoFotograma = tiempoFotograma;
<span class="tok-keyword">this</span>.parado = true;
}
<span class="tok-keyword">async</span> <span class="tok-definition">reproducir</span>() {
<span class="tok-keyword">this</span>.parado = 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>.parado; i++) {
<span class="tok-keyword">let</span> <span class="tok-definition">siguienteFotograma</span> = espera(<span class="tok-keyword">this</span>.tiempoFotograma);
<span class="tok-keyword">await</span> mostrarFotograma(<span class="tok-keyword">this</span>.fotogramas[i % <span class="tok-keyword">this</span>.fotogramas.length]);
<span class="tok-keyword">await</span> siguienteFotograma;
}
}
<span class="tok-definition">parar</span>() {
<span class="tok-keyword">this</span>.parado = true;
}
}</pre>
<p><a class="p_ident" id="p-wKK2/D6QRW" href="#p-wKK2/D6QRW" tabindex="-1" role="presentation"></a>La función <code>espera</code> envuelve <code>setTimeout</code> en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-GrUGoEJqTM" href="#c-GrUGoEJqTM" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">vídeo</span> = <span class="tok-keyword">new</span> ReproductorVídeo(imágenesVídeo, <span class="tok-number">100</span>);
vídeo.reproducir().catch(<span class="tok-definition">e</span> => {
console.log(<span class="tok-string">"La reproducción falló: "</span> + e);
});
setTimeout(() => vídeo.parar(), <span class="tok-number">15000</span>);</pre>
<p><a class="p_ident" id="p-gizPvmLjFs" href="#p-gizPvmLjFs" tabindex="-1" role="presentation"></a>Durante toda la semana que la pantalla permanece allí, todas las noches, cuando está oscuro, aparece misteriosamente un enorme pájaro naranja brillante en ella.</p>
<h2><a class="h_ident" id="h-iSkjGslyNf" href="#h-iSkjGslyNf" tabindex="-1" role="presentation"></a>El bucle de eventos</h2>
<p><a class="p_ident" id="p-cV+8xco+RZ" href="#p-cV+8xco+RZ" tabindex="-1" role="presentation"></a>Un programa asíncrono comienza ejecutando su script principal, que a menudo configurará callbacks para ser llamados más tarde. Ese script principal, así como las funciones de callback, se ejecutan por completo de una vez, sin interrupciones. Pero entre ellos, el programa puede estar inactivo, esperando a que ocurra algo.</p>
<p><a class="p_ident" id="p-RRuVVhfUCF" href="#p-RRuVVhfUCF" tabindex="-1" role="presentation"></a>Por lo tanto, las funciones de callback no son llamadas directamente por el código que las programó. Si llamo a <code>setTimeout</code> desde dentro de una función, esa función ya habrá retornado en el momento en que se llame a la función de callback de <code>setTimeout</code>. Y cuando la función de callback retorna, el control no vuelve a la función que lo programó.</p>
<p><a class="p_ident" id="p-dkmkAWYURU" href="#p-dkmkAWYURU" tabindex="-1" role="presentation"></a>El comportamiento asíncrono ocurre en su propia pila de llamadas vacía.</p>
<div class="translator-note"><p><strong>N. del T.:</strong> Esto último quiere decir que el comportamiento asíncrono en JavaScript no bloquea la ejecución: el código asíncrono se ejecuta una vez vaciada la pila de llamadas actual.</p>
</div>
<p><a class="p_ident" id="p-cspiD/byXw" href="#p-cspiD/byXw" tabindex="-1" role="presentation"></a>Esta es una de las razones por las que, sin promesas, gestionar excepciones en código asíncrono es tan difícil. Como cada callback comienza con una pila de llamadas en su mayoría vacía, sus manejadores de <code>catch</code> no estarán en la pila cuando lancen una excepción.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RZ/XhGQYYZ" href="#c-RZ/XhGQYYZ" 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">"¡Zoom!"</span>);
}, <span class="tok-number">20</span>);
} <span class="tok-keyword">catch</span> (<span class="tok-definition">e</span>) {
<span class="tok-comment">// Esto no se ejecutará</span>
console.log(<span class="tok-string">"Atrapado"</span>, e);
}</pre>
<p><a class="p_ident" id="p-joSObRYrFp" href="#p-joSObRYrFp" tabindex="-1" role="presentation"></a>No importa cuán cerca ocurran los eventos (como por ejemplo tiempos de espera o solicitudes entrantes), un entorno JavaScript ejecutará solo un programa a la vez. Puedes imaginártelo como un gran bucle, llamado el <em>bucle de eventos</em>, que se ejecuta <em>alrededor</em> de tu programa. Cuando no hay nada que hacer, ese bucle se pausa. Pero a medida que llegan eventos, se agregan a una cola y su código se ejecuta uno tras otro. Como no se ejecutan dos cosas al mismo tiempo, un código lento puede retrasar el manejo de otros eventos.</p>
<p><a class="p_ident" id="p-UiF6wXkNgm" href="#p-UiF6wXkNgm" tabindex="-1" role="presentation"></a>Este ejemplo establece un tiempo de espera pero luego se demora hasta después del momento previsto para el tiempo de espera, provocando que el tiempo de espera se alargue y termine más tarde de la cuenta.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6aSENtec1Q" href="#c-6aSENtec1Q" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">comienzo</span> = Date.now();
setTimeout(() => {
console.log(<span class="tok-string">"El tiempo de espera se ejecutó en"</span>, Date.now() - comienzo);
}, <span class="tok-number">20</span>);
<span class="tok-keyword">while</span> (Date.now() < comienzo + <span class="tok-number">50</span>) {}
console.log(<span class="tok-string">"Tiempo perdido hasta"</span>, Date.now() - comienzo);
<span class="tok-comment">// → Tiempo perdido hasta 50</span>
<span class="tok-comment">// → El tiempo de espera se ejecutó en 55</span></pre>
<p><a class="p_ident" id="p-rhhrRkqin4" href="#p-rhhrRkqin4" tabindex="-1" role="presentation"></a>Las promesas siempre se resuelven o se rechazan como un nuevo evento. Incluso si una promesa ya está resuelta, esperarla hará que su callback se ejecute después de que termine el script actual, en lugar de inmediatamente.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CpYlSjFfYU" href="#c-CpYlSjFfYU" tabindex="-1" role="presentation"></a>Promise.resolve(<span class="tok-string">"Hecho"</span>).then(console.log);
console.log(<span class="tok-string">"¡Yo primero!"</span>);
<span class="tok-comment">// → ¡Yo primero!</span>
<span class="tok-comment">// → Hecho</span></pre>
<p><a class="p_ident" id="p-qwnR1hGJw8" href="#p-qwnR1hGJw8" tabindex="-1" role="presentation"></a>En capítulos posteriores veremos varios tipos de eventos que se ejecutan en el bucle de eventos.</p>
<h2><a class="h_ident" id="h-SvhK10Vq/a" href="#h-SvhK10Vq/a" tabindex="-1" role="presentation"></a>Errores asíncronos</h2>
<p><a class="p_ident" id="p-DJqLCQdudP" href="#p-DJqLCQdudP" tabindex="-1" role="presentation"></a>Cuando tu programa se ejecuta de forma sincrónica, de una sola vez, no hay cambios de estado ocurriendo excepto aquellos que el programa mismo realiza. Para programas asíncronos esto es diferente: pueden tener <em>brechas</em> en su ejecución durante las cuales otro código puede correr.</p>
<p><a class="p_ident" id="p-RqcPRXTj8I" href="#p-RqcPRXTj8I" tabindex="-1" role="presentation"></a>Veamos un ejemplo. Esta es una función que intenta reportar el tamaño de cada archivo en un array de archivos, asegurándose de leerlos todos al mismo tiempo en lugar de secuencialmente.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ZmNSWd6V9T" href="#c-ZmNSWd6V9T" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">tamañosArchivos</span>(<span class="tok-definition">archivos</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">lista</span> = <span class="tok-string">""</span>;
<span class="tok-keyword">await</span> Promise.all(archivos.map(<span class="tok-keyword">async</span> <span class="tok-definition">nombreArchivo</span> => {
lista += nombreArchivo + <span class="tok-string">": "</span> +
(<span class="tok-keyword">await</span> archivoTexto(nombreArchivo)).length + <span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>;
}));
<span class="tok-keyword">return</span> lista;
}</pre>
<p><a class="p_ident" id="p-HiAwDFFOUL" href="#p-HiAwDFFOUL" tabindex="-1" role="presentation"></a>La parte <code>async nombreArchivo =></code> muestra cómo también se pueden hacer arrow functions <code>async</code> (funciones flecha asíncronas) colocando la palabra <code>async</code> delante de ellas.</p>
<p><a class="p_ident" id="p-pPIiTEafT4" href="#p-pPIiTEafT4" tabindex="-1" role="presentation"></a>El código no parece sospechoso de inmediato... mapea la función flecha <code>async</code> sobre el array de nombres, creando un array de promesas, y luego usa <code>Promise.all</code> para esperar a todas ellas antes de devolver la lista que construyen.</p>
<p><a class="p_ident" id="p-PrXuYzRu5U" href="#p-PrXuYzRu5U" tabindex="-1" role="presentation"></a>Sin embargo, el programa está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Y4YY7I2xCH" href="#c-Y4YY7I2xCH" tabindex="-1" role="presentation"></a>tamañosArchivos([<span class="tok-string">"planes.txt"</span>, <span class="tok-string">"lista_compra.txt"</span>])
.then(console.log);</pre>
<p><a class="p_ident" id="p-s9Q+INoyNl" href="#p-s9Q+INoyNl" tabindex="-1" role="presentation"></a>¿Puedes descubrir por qué?</p>
<p><a class="p_ident" id="p-GBG4on0Rh9" href="#p-GBG4on0Rh9" tabindex="-1" role="presentation"></a>El problema radica en el operador <code>+=</code>, que toma el valor <em>actual</em> de <code>lista</code> en el momento en que comienza a ejecutarse la instrucción y luego, cuando el <code>await</code> termina, establece el enlace <code>lista</code> como ese valor más la cadena agregada.</p>
<p><a class="p_ident" id="p-gKQGh439Yc" href="#p-gKQGh439Yc" tabindex="-1" role="presentation"></a>Pero entre el momento en que comienza a ejecutarse la instrucción y el momento en que termina, hay una brecha asíncrona. La expresión <code>map</code> se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores <code>+=</code> comienza desde una cadena vacía y acaba, cuando recupera la información del almacenamiento, estableciendo <code>lista</code> en el resultado de agregar su línea a la cadena vacía.</p>
<p><a class="p_ident" id="p-MBjCCi/2ci" href="#p-MBjCCi/2ci" tabindex="-1" role="presentation"></a>Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a <code>join</code> en el resultado de <code>Promise.all</code>, en lugar de construir la lista cambiando una variable. Como de costumbre, calcular nuevos valores es menos propenso a errores que cambiar valores existentes.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lJOKU1AENO" href="#c-lJOKU1AENO" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">tamañosArchivos</span>(<span class="tok-definition">archivos</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">líneas</span> = archivos.map(<span class="tok-keyword">async</span> <span class="tok-definition">nombreArchivo</span> => {
<span class="tok-keyword">return</span> nombreArchivo + <span class="tok-string">": "</span> +
(<span class="tok-keyword">await</span> archivoTexto(nombreArchivo)).length;
});
<span class="tok-keyword">return</span> (<span class="tok-keyword">await</span> Promise.all(líneas)).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-D5OZ7eAJk7" href="#p-D5OZ7eAJk7" tabindex="-1" role="presentation"></a>Errores como este son fáciles de cometer, especialmente al usar <code>await</code>, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía <em>explícita</em> de JavaScript (ya sea a través de callbacks, promesas o <code>await</code>) es que identificar estas brechas es relativamente fácil.</p>
<h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role="presentation"></a>Resumen</h2>
<p><a class="p_ident" id="p-cNHsSzr2ax" href="#p-cNHsSzr2ax" tabindex="-1" role="presentation"></a>La programación asíncrona hace posible expresar la espera de acciones de larga duración sin congelar todo el programa. Los entornos de JavaScript típicamente implementan este estilo de programación utilizando callbacks, funciones que se llaman cuando las acciones se completan. Un bucle de eventos programa estas funciones de callback para que se llamen cuando sea apropiado, una tras otra, de modo que su ejecución no se superponga.</p>
<p><a class="p_ident" id="p-/tyI2XiFVY" href="#p-/tyI2XiFVY" tabindex="-1" role="presentation"></a>La programación asíncrona se facilita gracias a las promesas, que son objetos que representan acciones que podrían completarse en el futuro, y las funciones <code>async</code>, que te permiten escribir un programa asíncrono como si fuera sincrónico.</p>
<h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2>
<h3><a class="i_ident" id="i-+uVmJAkWrn" href="#i-+uVmJAkWrn" tabindex="-1" role="presentation"></a>Momentos de tranquilidad</h3>
<p><a class="p_ident" id="p-C4ZGbkY1j7" href="#p-C4ZGbkY1j7" tabindex="-1" role="presentation"></a>Cerca del laboratorio de Carla hay una cámara de seguridad que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de vídeo cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo estar tranquila.</p>
<p><a class="p_ident" id="p-UQByW5u5se" href="#p-UQByW5u5se" tabindex="-1" role="presentation"></a>También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como los que proporciona <code>Date.now()</code>).</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-jjaZ48VGVD" href="#p-jjaZ48VGVD" tabindex="-1" role="presentation"></a>El archivo <code>"camera_logs.<wbr>txt"</code> contiene una lista de archivos de registro. Escribe una función asíncrona <code>activityTable(día)</code> que, para un día de la semana dado, devuelva un array de 24 números, uno para cada hora del día, que contenga la cantidad de observaciones de tráfico de red de la cámara vista en esa hora del día. Los días se identifican por número utilizando el sistema utilizado por <code>Date.getDay</code>, donde el domingo es 0 y el sábado es 6.</p>
<p><a class="p_ident" id="p-nq7jr1mWcA" href="#p-nq7jr1mWcA" tabindex="-1" role="presentation"></a>La función <code>activityGraph</code>, proporcionada por el sandbox, resume dicha tabla en una cadena.</p>
<p><a class="p_ident" id="p-4EjrIsWVmx" href="#p-4EjrIsWVmx" tabindex="-1" role="presentation"></a>Utiliza la función <code>textFile</code> ( o <code>archivoTexto</code>) definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que <code>new Date(marcaDeTiempo)</code> crea un objeto <code>Date</code> para ese momento, que tiene métodos <code>getDay</code> y <code>getHours</code> que devuelven el día de la semana y la hora del día.</p>
<p><a class="p_ident" id="p-oVU7j47Kad" href="#p-oVU7j47Kad" tabindex="-1" role="presentation"></a>Ambos tipos de archivos —la lista de archivos de registro y los propios archivos de registro— tienen cada dato en una línea, separados por caracteres de nueva línea (<code>"\n"</code>).</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AjUhoPZKxz" href="#c-AjUhoPZKxz" 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">día</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">// Tu código aquí</span>
}
activityTable(<span class="tok-number">1</span>)
.then(<span class="tok-definition">table</span> => console.log(activityGraph(table)));</pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-jZ7OykMDrT" href="#p-jZ7OykMDrT" tabindex="-1" role="presentation"></a>Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método <code>split</code> en la cadena producida por <code>textFile</code> ( o <code>archivoTexto</code>). Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a <code>new Date</code>.</p>
<p><a class="p_ident" id="p-9fBqjU8ju+" href="#p-9fBqjU8ju+" tabindex="-1" role="presentation"></a>Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todas las marcas de tiempo (de los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, tomar la hora en que ocurrió y sumar uno al número correspondiente en la tabla.</p>
<p><a class="p_ident" id="p-igRmA9qYWA" href="#p-igRmA9qYWA" tabindex="-1" role="presentation"></a>Asegúrate de usar <code>await</code> en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una <code>Promise</code> donde esperabas tener un string.</p>
</div></details>
<h3><a class="i_ident" id="i-rAhebsaSY2" href="#i-rAhebsaSY2" tabindex="-1" role="presentation"></a>Promesas Reales</h3>
<p><a class="p_ident" id="p-vfx72TWCka" href="#p-vfx72TWCka" tabindex="-1" role="presentation"></a>Reescribe la función del ejercicio anterior sin <code>async</code>/<code>await</code>, utilizando métodos simples de <code>Promise</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-W6rAArkArS" href="#c-W6rAArkArS" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) {
<span class="tok-comment">// Tu código aquí</span>
}
activityTable(<span class="tok-number">6</span>)
.then(<span class="tok-definition">tabla</span> => console.log(activityGraph(tabla)));</pre>
<p><a class="p_ident" id="p-BGph0/UdzK" href="#p-BGph0/UdzK" tabindex="-1" role="presentation"></a>En este estilo, usar <code>Promise.all</code> será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función <code>async</code>, simplemente usar <code>await</code> en un bucle es más simple. Si leer un archivo lleva un tiempo, ¿cuál de estos dos enfoques necesitará menos tiempo para ejecutarse?</p>
<p><a class="p_ident" id="p-EBStIx8OmH" href="#p-EBStIx8OmH" tabindex="-1" role="presentation"></a>Si uno de los archivos listados en la lista de archivos tiene un error tipográfico, y su lectura falla, ¿cómo termina ese fallo en el objeto <code>Promise</code> que retorna tu función?</p>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-aw5MLt4jxJ" href="#p-aw5MLt4jxJ" tabindex="-1" role="presentation"></a>El enfoque más directo para escribir esta función es usar una cadena de llamadas <code>then</code>. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear <code>textFile</code> sobre ella para obtener un array de promesas para pasar a <code>Promise.all</code>. Puede devolver el objeto devuelto por <code>Promise.all</code>, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer <code>then</code>.</p>
<p><a class="p_ident" id="p-XLfKUPx5h8" href="#p-XLfKUPx5h8" tabindex="-1" role="presentation"></a>Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a <code>then</code> nuevamente en eso, y poner la lógica de recuento de marcas de tiempo allí. Algo así:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-XVz2i5dZDr" href="#c-XVz2i5dZDr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) {
<span class="tok-keyword">return</span> archivoTexto(<span class="tok-string">"camera_logs.txt"</span>).then(<span class="tok-definition">archivos</span> => {
<span class="tok-keyword">return</span> Promise.all(archivos.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(archivoTexto));
}).then(<span class="tok-definition">logs</span> => {
<span class="tok-comment">// analizar...</span>
});
}</pre>
<p><a class="p_ident" id="p-AKUrRuWxiy" href="#p-AKUrRuWxiy" tabindex="-1" role="presentation"></a>O podrías, para una programación del trabajo aún mejor, poner el análisis de cada archivo dentro de <code>Promise.all</code>, para que ese trabajo pueda comenzar con el primer archivo que se reciba del disco, incluso antes de que lleguen los otros archivos.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-M7pmLIzx0/" href="#c-M7pmLIzx0/" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">tabla</span> = []; <span class="tok-comment">// inicializar...</span>
<span class="tok-keyword">return</span> archivoTexto(<span class="tok-string">"registros_camara.txt"</span>).then(<span class="tok-definition">archivos</span> => {
<span class="tok-keyword">return</span> Promise.all(archivos.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(<span class="tok-definition">nombre</span> => {
<span class="tok-keyword">return</span> archivoTexto(nombre).then(<span class="tok-definition">log</span> => {
<span class="tok-comment">// analizar...</span>
});
}));
}).then(() => tabla);
}</pre>
<p><a class="p_ident" id="p-AoWOB0oS8h" href="#p-AoWOB0oS8h" tabindex="-1" role="presentation"></a>Esto demuestra que la forma en que estructuras tus promesas puede tener un efecto real en la forma en que se programa el trabajo. Un simple bucle con <code>await</code> hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. <code>Promise.all</code> hace posible que varias tareas sean trabajadas conceptualmente al mismo tiempo, permitiéndoles progresar mientras los archivos aún se están cargando. Esto puede ser más rápido, pero también hace que el orden en que sucederán las cosas sea menos predecible. En este caso, donde solo vamos a estar incrementando números en una tabla, eso no es difícil de hacer de manera segura. Para otros tipos de problemas, puede ser mucho más difícil.</p>
<p><a class="p_ident" id="p-Ld+4EByL4v" href="#p-Ld+4EByL4v" tabindex="-1" role="presentation"></a>Cuando un archivo en la lista no existe, la promesa devuelta por <code>archivoTexto</code> será rechazada. Debido a que <code>Promise.all</code> se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la callback dada al primer <code>then</code> también será una promesa rechazada. Esto hace que la promesa devuelta por <code>then</code> falle, por lo que la callback dada al segundo <code>then</code> ni siquiera se llama, y se devuelve una promesa rechazada desde la función.</p>
</div></details>
<h3><a class="i_ident" id="i-pfc5Y5gAWn" href="#i-pfc5Y5gAWn" tabindex="-1" role="presentation"></a>Construyendo Promise.all</h3>
<p><a class="p_ident" id="p-M8QzvSw1av" href="#p-M8QzvSw1av" tabindex="-1" role="presentation"></a>Como vimos, dado un array de promesas, <code>Promise.all</code> devuelve una promesa que espera a que todas las promesas en el array finalicen. Luego tiene éxito, devolviendo un array de valores de resultado. Si una promesa en el array falla, la promesa devuelta por <code>all</code> también falla, con la razón de fallo de la promesa que falló.</p>
<p><a class="p_ident" id="p-jbVLP1YRLs" href="#p-jbVLP1YRLs" tabindex="-1" role="presentation"></a>Implementa algo similar tú mismo como una función normal llamada <code>Promise_all</code>.</p>
<p><a class="p_ident" id="p-JLqRagv2ZP" href="#p-JLqRagv2ZP" tabindex="-1" role="presentation"></a>Recuerda que después de que una promesa tiene éxito o falla, no puede volver a tener éxito o fallar, y las llamadas posteriores a las funciones que la resuelven se ignoran. Esto puede simplificar la forma en que manejas el fallo de tu promesa.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ksC0e2Z7aT" href="#c-ksC0e2Z7aT" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">Promise_all</span>(<span class="tok-definition">promesas</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolver</span>, <span class="tok-definition">rechazar</span>) => {
<span class="tok-comment">// Tu código aquí.</span>
});
}
<span class="tok-comment">// Código de prueba.</span>
Promise_all([]).then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"Esto debería ser []:"</span>, array);
});
<span class="tok-keyword">function</span> <span class="tok-definition">pronto</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([pronto(<span class="tok-number">1</span>), pronto(<span class="tok-number">2</span>), pronto(<span class="tok-number">3</span>)]).then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"Esto debería ser [1, 2, 3]:"</span>, array);
});
Promise_all([pronto(<span class="tok-number">1</span>), Promise.reject(<span class="tok-string">"X"</span>), pronto(<span class="tok-number">3</span>)])
.then(<span class="tok-definition">array</span> => {
console.log(<span class="tok-string">"No deberíamos llegar aquí"</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">"Fallo inesperado:"</span>, error);
}
});</pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-jsnP1W7uln" href="#p-jsnP1W7uln" tabindex="-1" role="presentation"></a>La función pasada al constructor <code>Promise</code> tendrá que llamar a <code>then</code> en cada una de las promesas en el array dado. Cuando una de ellas tiene éxito, dos cosas deben suceder: el valor resultante debe ser almacenado en la posición correcta de un array de resultados, y debemos verificar si esta era la última promesa pendiente y finalizar nuestra propia promesa si lo era.</p>
<p><a class="p_ident" id="p-pv4cygIqmH" href="#p-pv4cygIqmH" tabindex="-1" role="presentation"></a>Esto último se puede hacer con un contador que se inicializa con la longitud del array de entrada y del cual restamos 1 cada vez que una promesa tiene éxito. Cuando llegue a 0, hemos terminado. Asegúrate de tener en cuenta la situación en la que el array de entrada está vacío (y por lo tanto ninguna promesa se resolverá nunca).</p>
<p><a class="p_ident" id="p-RfvboDBLxx" href="#p-RfvboDBLxx" tabindex="-1" role="presentation"></a>Manejar el fallo requiere un poco de pensamiento pero resulta ser extremadamente simple. Simplemente pasa la función <code>reject</code> de la promesa contenedora a cada una de las promesas en el array como un controlador <code>catch</code> o como un segundo argumento para <code>then</code> para que un fallo en una de ellas desencadene el rechazo de toda la promesa contenedora.</p>
<p><a class="p_ident" id="p-2jmj7l5rSw" href="#p-2jmj7l5rSw" tabindex="-1" role="presentation"></a>}}</p>
</div></details><nav><a href="10_modules.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="12_language.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
</article>
<script src="ejs.js"></script>