Cómo comenzar rápidamente con VUE3.0: ¿Aprenda
Aunque JavaScript es de un solo subproceso, el bucle de eventos utiliza el kernel del sistema tanto como sea posible, lo que permite a Node.js realizar operaciones de E/S sin bloqueo. Aunque la mayoría de los núcleos modernos son de subprocesos múltiples, pueden manejar tareas de subprocesos múltiples en el. fondo. Cuando se completa una tarea, el kernel le informa a Node.js y luego se agregará la devolución de llamada adecuada al bucle para su ejecución. Este artículo presentará este tema con más detalle.
Cuando Node.js inicia la ejecución, el bucle de eventos. primero se inicializará. , procesará el script de entrada proporcionado (o lo colocará en REPL, que no se trata en este documento). Esto realizará una llamada API asincrónica, programará un temporizador o llamará a process.nextTick() y luego. comience a procesar el bucle de eventos La
siguiente figura muestra la secuencia de ejecución del bucle de eventos
┌──────────────────────────┐.
┌─>│ temporizadores │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ devoluciones de llamada pendientes │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ inactivo, prepárate │ │ └─────────────┬────────────┘ ┌────────── ──────┐ │ ┌─────────────┴────────────┐ │ entrante: │ │ │ encuesta │<─────┤ conexiones, │ │ └─────────────┬─────────────┘ │ datos, etc. │ ┌─────────────┴────────────┐ └────────── ──────┘ │ │ comprobar │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ cerrar devoluciones de llamada │ └────────────────────────────┘Cada
cuadro representa una etapa del bucle de eventos.
Sin embargo, cada etapa tiene una ejecución de devolución de llamada de cola FIFO. , cada etapa se ejecuta a su manera. En términos generales, cuando el bucle de eventos ingresa a una etapa, realizará cualquier operación en la etapa actual y comenzará a ejecutar devoluciones de llamada en la cola de la etapa actual hasta que la cola se consuma o ejecute por completo. los datos máximos. Cuando la cola se agota o alcanza su tamaño máximo, el bucle de eventos pasa a la siguiente fase.
TemporizadoresEn cada proceso del bucle de eventos, Node .js comprueba si está esperando E/S asincrónicas y un temporizador, y si no, cierra
Un temporizador especifica el punto crítico en el que se ejecutará una devolución de llamada, en lugar del tiempo deseado
.El temporizador se ejecutará lo antes posible después de transcurrido el tiempo especificado; sin embargo, la programación del sistema operativo u otras devoluciones de llamada pueden retrasar la ejecución.
Técnicamente hablando, la fase de sondeo determina cuándo se ejecuta la devolución de llamada.
Por ejemplo, configura un temporizador para que se ejecute después de 100 ms, pero su script lee un archivo de forma asíncrona y tarda 95 ms
const fs = require('fs') ; función algunaAsyncOperation (devolución de llamada) { // Supongamos que esto tarda 95 ms en completarse fs.readFile('/ruta/al/archivo', devolución de llamada); } const timeoutScheduled = Fecha.ahora(); setTimeout(() => { retraso constante = Date.now() - timeoutScheduled; console.log(`${delay}ms han pasado desde que estaba programado`); }, 100); // realiza alguna operación Async que tarda 95 ms en completarse algunaOperaciónAsync(() => { const startCallback = Fecha.ahora(); // haz algo que tomará 10 ms... mientras (Fecha.ahora() - startCallback < 10) { // no hacer nada } });
cuando el bucle de eventos ingresa a la fase de sondeo, hay una cola vacía (fs.readFile () aún no se ha completado), por lo que esperará los milisegundos restantes hasta que se alcance el umbral del temporizador más rápido. Después de 95 ms, fs. .readFile() ha completado la lectura del archivo y tardará 10 ms en agregarse a la fase de sondeo y completar la ejecución. Cuando se completa la devolución de llamada, no hay devoluciones de llamada en la cola para ejecutarse y el bucle de eventos regresa a la fase de temporizadores. para ejecutar la devolución de llamada del temporizador. En este ejemplo, verá que el temporizador se retrasa 105 ms antes de ejecutarse.
Para evitar que la fase de sondeo bloquee el bucle de eventos, libuv (la biblioteca de lenguaje C que implementa el bucle de eventos y todo el comportamiento asincrónico en la plataforma) también tiene. una fase de sondeo. max deja de sondear más eventos
Esta fase ejecuta devoluciones de llamada para ciertas operaciones del sistema (como tipos de errores de TCP). Por ejemplo, algunos sistemas *nix quieren esperar a que se informe de un error si un socket TCP recibe ECONNREFUSED al intentar conectarse. Esto se pondrá en cola para su ejecución durante la fase de devolución de llamada pendiente.
La fase de sondeo tiene dos funciones principales
el bucle de eventos ingresa a la fase de sondeo y no hay temporizador, suceden las dos cosas siguientes.
la
detectará si el temporizador ha expirado. Si es así, el bucle de eventos llegará a la etapa de temporizadores y ejecutará la devolución de llamada del temporizador
.esta etapa. Permite a las personas ejecutar devoluciones de llamada inmediatamente después de que se complete la fase de encuesta. Si la fase de sondeo queda inactiva y el script se ha puesto en cola usando setImmediate(), el bucle de eventos puede continuar hasta la fase de verificación en lugar de esperar.
setImmediate() es en realidad un temporizador especial que se ejecuta en una fase separada del bucle de eventos. Utiliza una API libuv para programar las devoluciones de llamadas que se ejecutarán después de que se complete la fase de encuesta.
Normalmente, a medida que se ejecuta el código, el bucle de eventos finalmente llega a la fase de sondeo, donde espera conexiones entrantes, solicitudes, etc. Sin embargo, si se programa una devolución de llamada usando setImmediate() y la fase de sondeo queda inactiva, finalizará y continuará con la fase de verificación en lugar de esperar el evento de sondeo.
Si un socket u operación se cierra repentinamente (por ejemplo, socket.destroy()), el evento de cierre se enviará a esta etapa; de lo contrario, se enviará a través de process.nextTick() setImmediate
setImmediate() y setTimeout() son similares, pero se comportan de manera diferente dependiendo de cuándo se llaman
El orden en el que se ejecuta cada devolución de llamada depende de el orden en que se llaman En este entorno, si se llama al mismo módulo al mismo tiempo, el tiempo estará limitado por el rendimiento del proceso (esto también se verá afectado por otras aplicaciones que se ejecutan en esta máquina,
por ejemplo
)., si no ejecutamos el siguiente script en I/O, aunque se ve afectado por el rendimiento del proceso, no es posible determinar el orden de ejecución de estos dos temporizadores:
// timeout_vs_immediate.js setTimeout(() => { console.log('tiempo de espera'); }, 0); setImmediate(() => { console.log('inmediato'); });
$ nodo timeout_vs_immediate.js se acabó el tiempo inmediato $ nodo timeout_vs_immediate.js inmediato timeout
Sin embargo, si ingresa al bucle de E/S, la devolución de llamada inmediata siempre se ejecutará primero
// timeout_vs_immediate.js const fs = requerir('fs'); fs.readFile(__nombre de archivo, () => { setTimeout(() => { console.log('tiempo de espera'); }, 0); setImmediate(() => { console.log('inmediato'); }); });
$ nodo timeout_vs_immediate.js inmediato se acabó el tiempo $ nodo timeout_vs_immediate.js inmediatoLa ventaja del
tiempo de espera
setImmediate sobre setTimeout es que setImmediate siempre se ejecutará primero antes que cualquier temporizador en E/S, independientemente de cuántos temporizadores existan.
Aunque Process.nextTick() es parte de la API asincrónica, es posible que hayas notado que no aparece en el diagrama. Esto se debe a que Process.nextTick() no es parte de la tecnología de bucle de eventos. se ejecuta la operación actual Una vez completada, nextTickQueue se ejecutará independientemente de la etapa actual del bucle de eventos. Aquí, las operaciones se definen como transformaciones del controlador C/C++ subyacente y manejan el JavaScript que debe ejecutarse. Según el diagrama, puede llamar a Process.nextTick() en cualquier etapa. Todas las devoluciones de llamada pasadas a Process.nextTick() se ejecutarán antes de que el bucle de eventos continúe con la ejecución. Esto puede provocar algunas situaciones malas porque le permite llamar de forma recursiva. . Process.nextTick() "mata de hambre" su E/S, lo que evita que el bucle de eventos entre en la fase de sondeo.
¿Por qué se incluye esta situación en Node.js? Dado que la filosofía de diseño de Node.js es que una API siempre debe ser asincrónica incluso si no tiene por qué serlo, observe el siguiente fragmento de
función apiCall(arg, callback) { si (tipo de argumento! == 'cadena') proceso de devolución.nextTick( llamar de vuelta, nuevo TypeError('el argumento debe ser una cadena') ); }
El fragmento verifica los parámetros y, si es incorrecto, pasa el error a la devolución de llamada. La API se actualizó recientemente para permitir pasar parámetros a Process.nextTick(), lo que le permite aceptar cualquier parámetro pasado después de la devolución de llamada como argumento para la devolución de llamada, por lo que no es necesario anidar funciones.
Lo que estamos haciendo es devolver el error al usuario, pero solo si permitimos que se ejecute el resto del código del usuario. Al usar Process.nextTick(), nos aseguramos de que apiCall() siempre ejecute su devolución de llamada después del resto del código de usuario y antes de permitir que continúe el bucle de eventos. Para lograr esto, se permite que la pila de llamadas JS se desenrolle y luego la devolución de llamada proporcionada se ejecuta inmediatamente, lo que permite realizar llamadas recursivas a Process.nextTick() sin alcanzar RangeError: se excedió el tamaño máximo de la pila de llamadas a partir de v8.