Este artículo es compartido por la comunidad de la nube de Huawei " Semana de lectura de marzo·JavaScript que no conoces | Generador ES6, un estilo de expresión de control de proceso asincrónico aparentemente sincrónico ", autor: Ye Yiyi.
Constructor
romper la carrera completa
Existe una suposición en la que los desarrolladores de JavaScript confían casi universalmente en su código: una vez que una función comienza a ejecutarse, se ejecutará hasta el final y ningún otro código puede interrumpirla e insertarla en el medio.
ES6 introduce un nuevo tipo de función que no se ajusta a esta característica de ejecución completa. Este nuevo tipo de función se llama generador.
var x = 1; función foo() { x++; bar(); // <-- esta línea se ejecuta entre las declaraciones x++ y console.log(x) console.log('x:', x); } barra de funciones() { x++; } foo(); // x: 3
¿Qué pasa si bar() no está ahí? Obviamente el resultado será 2, no 3. El resultado final es 3, por lo que bar() se ejecutará entre x++ y console.log(x).
Pero JavaScript no es preventivo ni multiproceso (todavía). Sin embargo, aún sería posible implementar tales interrupciones de manera cooperativa (concurrencia) si foo() pudiera de alguna manera indicar una pausa en este punto del código.
Aquí está el código ES6 para implementar la concurrencia cooperativa:
var x = 1; función* foo() { x++; ceder; // ¡Pausa! console.log('x:', x); } barra de funciones() { x++; } //Construye un iterador para controlar este generador. var it = foo(); //¡Inicia foo() aquí! it.siguiente(); console.log('x:', x); // 2 bar(); console.log('x:', x); // 3 it.siguiente(); // x: 3
- La operación it = foo() no ejecuta el generador *foo(), sino que solo construye un iterador (iterador), que controla su ejecución.
- *foo() hace una pausa en la declaración de rendimiento, momento en el que finaliza la primera llamada a it.next(). En este punto *foo() todavía está ejecutándose y activo, pero en estado suspendido.
- La llamada final a it.next() reanuda la ejecución del generador *foo() desde donde se pausó y ejecuta la instrucción console.log(..), que utiliza el valor actual de x, 3.
Un generador es un tipo especial de función que se puede iniciar y detener una o más veces, pero no necesariamente debe completarse.
entrada y salida
Una función generadora es una función especial que todavía tiene algunas propiedades básicas de las funciones. Por ejemplo, aún puede aceptar parámetros (es decir, entrada) y devolver valores (es decir, salida).
función* foo(x, y) { devolver x * y; } var it = foo(6, 7); var res = it.siguiente(); valor res.; // 42
Pase los parámetros reales 6 y 7 a *foo(..) como parámetros xey respectivamente. *foo(..) devuelve 42 al código de llamada.
iteradores múltiples
Cada vez que construye un iterador, en realidad construye implícitamente una instancia del generador, que es la instancia del generador que se controla a través de este iterador.
Se pueden ejecutar varias instancias del mismo generador simultáneamente e incluso pueden interactuar entre sí:
función* foo() { var x = rendimiento 2; z++; var y = rendimiento x * z; consola.log(x, y, z); } var z = 1; var it1 = foo(); var it2 = foo(); var val1 = it1.next().valor; // 2 <-- rendimiento 2 var val2 = it2.next().valor; // 2 <-- rendimiento 2 val1 = it1.next(val2 * 10).valor; // 40 <-- x:20, z:2 val2 = it2.next(val1 * 5).valor; // 600 <-- x:200, z:3 it1.siguiente(val2/2); // y:300 // 20300 3 it2.next(val1/4); // y:10 // 200 10 3
Resuma brevemente el proceso de ejecución:
(1) Se inician dos instancias de *foo() al mismo tiempo, y las dos next() obtienen el valor 2 de la declaración de rendimiento 2 respectivamente.
(2) val2 * 10, que es 2 * 10, se envía a la primera instancia del generador it1, por lo que x obtiene el valor 20. z aumenta de 1 a 2, luego se emite 20 * 2 a través del rendimiento, estableciendo val1 en 40.
(3) val1 * 5, que es 40 * 5, se envía a la segunda instancia del generador it2, por lo que x obtiene el valor 200. z se incrementa de 2 a 3 nuevamente, luego se emite 200 * 3 a través del rendimiento, configurando val2 en 600.
(4) val2/2, que es 600/2, se envía a la primera instancia del generador it1, por lo que y obtiene el valor 300, y luego los valores de xyz se imprimen como 20300 3 respectivamente.
(5) val1/4, que es 40/4, se envía a la segunda instancia del generador it2, por lo que y obtiene el valor 10, y luego los valores de xyz se imprimen como 200 10 3 respectivamente.
El generador produce valor
Productores e iteradores
Supongamos que desea generar una secuencia de valores, cada uno de los cuales tiene una relación específica con el anterior. Lograr esto requiere un productor con estado que pueda recordar el último valor que produjo.
Un iterador es una interfaz bien definida para obtener una secuencia de valores de un productor paso a paso. La interfaz del iterador de JavaScript consiste en llamar a next() cada vez que desee obtener el siguiente valor del productor.
La interfaz de iterador estándar se puede implementar para generadores de secuencias numéricas:
var algo = (funcion () { var siguienteValido; devolver { // se requiere bucle for..of [Símbolo.iterador]: función () { devolver esto; }, // Método de interfaz de iterador estándar siguiente: función () { si (nextVal === indefinido) { siguienteVal = 1; } demás { valorsiguiente = 3 * valorsiguiente + 6; } return {hecho: falso, valor: nextVal}; }, }; })(); algo.siguiente().valor; // 1 algo.siguiente().valor; // 9 algo.siguiente().valor; // 33 algo.siguiente().valor; // 105
La llamada next() devuelve un objeto. Este objeto tiene dos propiedades: hecho es un valor booleano que identifica el estado de finalización del valor del iterador y coloca el valor de la iteración.
iterable
iterable es un objeto que contiene un iterador que se puede iterar sobre sus valores.
A partir de ES6, la forma de extraer un iterador de un iterable es que el iterable debe admitir una función cuyo nombre sea el valor de símbolo especializado de ES6 Symbol.iterator. Cuando se llama a esta función, devuelve un iterador. Normalmente cada llamada devuelve un nuevo iterador, aunque esto no es necesario.
var a = [1, 3, 5, 7, 9]; para (var v de a) { consola.log(v); } // 1 3 5 7 9
a en el fragmento de código anterior es iterable. El bucle for..of llama automáticamente a su función Symbol.iterator para construir un iterador.
for (var v de algo) { .. }
El bucle for..of espera que algo sea iterable, por lo que busca y llama a su función Symbol.iterator.
iterador generador
El generador puede considerarse como un productor de valores. Extraemos un valor a la vez mediante la llamada next() de la interfaz del iterador.
Los generadores en sí no son iterables. Cuando ejecutas un generador, obtienes un iterador:
función *foo(){ .. } var it = foo();
El anterior productor de secuencia de números infinitos se puede implementar a través de un generador, similar a este:
función* algo() { var siguienteValido; mientras (verdadero) { si (nextVal === indefinido) { siguienteVal = 1; } demás { valorsiguiente = 3 * valorsiguiente + 6; } producir nextVal; } }
Debido a que el generador se detiene en cada rendimiento, el estado (alcance) de la función *algo() se mantiene, lo que significa que no se necesita ningún cierre para mantener el estado variable entre llamadas.
Generador de iteradores asíncronos
función foo(x, y) { ajax('http://some.url.1/? x=' + x + '&y=' + y, función (err, datos) { si (errar) { // Lanza un error a *main() it.throw(err); } demás { //Restaurar *main() con los datos recibidos it.siguiente(datos); } }); } función* principal() { intentar { var texto = rendimiento foo(11, 31); consola.log(texto); } atrapar (errar) { consola.error(err); } } var it = principal(); //¡Empieza aqui! it.siguiente();
En Yield foo(11,31), primero se llama a foo(11,31), lo que no devuelve ningún valor (es decir, devuelve indefinido), por lo que se realiza una llamada para solicitar los datos, pero lo que realmente se hace después es Yield indefinido.
El rendimiento no se usa aquí en el sentido de pasar mensajes, sino que solo se usa para el control de flujo para implementar la pausa/bloqueo. De hecho, todavía se transmitirán mensajes, pero solo será un mensaje unidireccional después de que el generador reanude su funcionamiento.
Echa un vistazo a foo(...). Si esta solicitud Ajax tiene éxito, llamamos:
it.siguiente(datos);
Esto reanuda el generador con los datos de respuesta, lo que significa que la expresión de rendimiento pausada recibió este valor directamente. Luego, este valor se asigna al texto de la variable local mientras el código del generador continúa ejecutándose.
Resumir
Resumamos los contenidos principales de este artículo:
- Generator es un nuevo tipo de función en ES6 y no siempre se ejecuta hasta el final como las funciones normales. En cambio, un generador se puede pausar mientras está en funcionamiento (preservando su estado por completo) y reanudarlo desde donde se pausó en un momento posterior.
- El par de rendimiento/siguiente(..) no es sólo un mecanismo de control, sino también un mecanismo de paso de mensajes bidireccional. producir .. Básicamente, la expresión se detiene y espera un valor, y la siguiente llamada next(..) devuelve un valor (o implícitamente indefinido) a la expresión de rendimiento pausada.
- La ventaja clave de los generadores cuando se trata de flujo de control asincrónico es que el código dentro del generador es una secuencia de pasos que expresan una tarea de una manera naturalmente sincrónica/secuencial. El truco consiste en ocultar la posible asincronía detrás de la palabra clave rendimiento y mover la asincronía a la parte del código que controla el iterador del generador.
- Los generadores mantienen un patrón de código de bloqueo secuencial, sincrónico para el código asincrónico, lo que permite que el cerebro siga el código de forma más natural, resolviendo uno de los dos defectos clave de la asincronía basada en devolución de llamadas.
Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud lo antes posible ~
La primera actualización importante de JetBrains 2024 (2024.1) es de código abierto. Incluso Microsoft planea pagar por ella. ¿Por qué todavía se le critica por ser de código abierto? [Recuperado] El backend de Tencent Cloud falló: una gran cantidad de errores de servicio y no hay datos después de iniciar sesión en la consola. Alemania también necesita ser "controlable de forma independiente". El gobierno estatal migró 30,000 PC de Windows a Linux deepin-IDE y finalmente logró ¡arranque! Se lanza Visual Studio Code 1.88. Buen chico, Tencent realmente ha convertido a Switch en una "máquina de aprendizaje pensante". El escritorio remoto de RustDesk inicia y reconstruye el cliente web. La base de datos de terminal de código abierto de WeChat basada en SQLite, WCDB, ha recibido una actualización importante.