Sé que la función de devolución de llamada se ejecuta de forma asincrónica, pero ¿por qué es eso?

La ejecución asincrónica de una función de devolución de llamada no es una característica del lenguaje. Depende únicamente de la implementación de la función en cuestión.

Las funciones en JavaScript son valores. Eso significa que pueden ser manipulados y pasados ​​dentro y fuera de funciones sin necesariamente ser llamados. Cuando vincula un nombre a una expresión de función, por ejemplo, var myFunc = function () {} , el nombre se refiere a un objeto Function invocable. Para llamar al invocable, debe proporcionar la lista de argumentos, incluso cuando está vacía. Por lo tanto, una expresión de función que no es seguida por paréntesis es válida pero no se ejecuta. La llamada equivalente para una expresión de función que no está vinculada a un nombre es casi idéntica.

(function () {})()
o
(function () {} ())

El conjunto adicional de paréntesis no está estrictamente definido en la especificación. La mayoría de las veces es el azúcar sintáctico para que el motor JS sepa que la palabra clave de function se usa en el contexto de una expresión en lugar de una definición de función.

El caso al pasar funciones de devolución de llamada es, por lo tanto, que las pasamos como expresiones de funciones no ejecutadas. A partir de ese momento, corresponde a la persona que llama decidir qué hacer con la devolución de llamada.

Pueden decidir llamarlo sincrónicamente simplemente llamando al argumento recibido (por ejemplo, callback() , donde callback es el nombre vinculado internamente al valor de devolución de llamada).

En su lugar, podrían llamarlo en una iteración futura del bucle de eventos (por ejemplo, utilizando escuchas de eventos en el navegador o process.nextTick() en el nodo). El nodo mismo lo hace internamente en su implementación de C ++ por razones de rendimiento.

Incluso podrían ignorarlo por completo y no llamarlo en absoluto. Todo depende de la persona que llama.

Dicho esto, en la mayoría de los casos, una devolución de llamada ejecutada sincrónicamente es probablemente un signo revelador de un diseño deficiente.

No hay nada acerca de la sintaxis que hace que se ejecute de forma asincrónica. Pasar una función como argumento no equivale a una ejecución asincrónica; por ejemplo, el método Array.prototype.forEach toma un argumento de función y se ejecuta de forma completamente sincrónica.

La razón por la que ciertos métodos se ejecutan de forma asíncrona en JavaScript es porque son métodos de interfaz de API web, que son manejados por un grupo de subprocesos que se ejecuta en el navegador fuera del entorno de tiempo de ejecución de JavaScript normal. Algunas otras partes comúnmente utilizadas de la interfaz de la API web son setTimeout (para agregar asincrónicamente devoluciones de llamada basadas en el tiempo a la cola de eventos) y XMLHttpRequest (para solicitudes AJAX). Básicamente, todo el código sobre el que escribe o tiene control se ejecuta en un solo subproceso, pero el procesamiento asincrónico aún es posible a través de las API web.

Para node.js, en lugar de las API web que manejan el procesamiento asincrónico, es manejado por una biblioteca llamada libuv.

Eche un vistazo a esta presentación extremadamente reveladora sobre este mismo tema:
¿Qué diablos es el bucle de eventos de todos modos? – Philip Roberts

La función que se llama, fs.readFile (), llama al sistema operativo para leer el archivo. Puede generar un nuevo hilo para hacerlo, o usar un grupo de hilos, o lo que sea. Realmente no te importa.

Desde el punto de vista de usted, el programador de Javascript, todo sucede en un solo hilo. Se llamará a la función anónima que proporcione cuando los datos estén listos. La sintaxis que lo hace posible es simplemente que se proporciona una función de devolución de llamada. Sin embargo, algunas funciones, como Array.sort (), reciben una función de devolución de llamada pero no la realizan de forma asincrónica, en ese caso se llamará a su función de devolución de llamada (varias veces, en el caso de la ordenación) antes de que la función de clasificación vuelva a llamador. No puede distinguir esto de la sintaxis, depende de cómo se implemente la función.

La pregunta es sobre Javascript. No es específico de node.js, pero en general,

var fs = require (‘fs’);
var ruta = proceso.argv [2];

fs.readFile (ruta, ‘utf8’, función (err, data) {
var líneas = data.split (‘\ n’);
console.log (lines.length-1);
} );

El contenido en negrita se llama función anónima (o Lambda). Cuando se llama, depende de la semántica de la función a la que la está pasando. Por ejemplo, la función node.js fs.readFile se escribe para que la devolución de llamada se llame de forma asíncrona cuando el contenido está disponible.

Del mismo modo, la función estándar Javascript setTimeout se comporta de manera similar.

Sin embargo, puedo, por ejemplo, definir una función como esta:

función foo (a) {
una();
}

donde la lambda pasó a foo no se llama asincrónicamente.

Puede probar esto fácilmente con un código como este (para JavaScript del navegador):

función foo (a) {
una();
}

alerta (‘1’);
foo (function () {alert (‘uno’);});
alerta (‘2’);
setTimeout (function () {alert (‘two’);}, 0);
alerta (‘3’);

El resultado es:

1
uno
2
3
dos

Está bastante claro que la lambda pasada a foo se llama sincrónicamente, y la lambda pasada a setTimeout se llama asíncronamente.

La respuesta a por qué se llama a la lambda de forma asíncrona en su ejemplo específico es porque la semántica de readFile es tal que la lambda se llama de forma asincrónica cuando hay datos disponibles.

Básicamente, el módulo fs y otros módulos IO en el nodo son “mágicos”.

JavaScript del nodo es ejecutado por el motor V8, escrito en C ++. Funciona en un bucle de eventos: cualquier javascript en ejecución puede agregar eventos a la cola, pero no se ejecutarán hasta que finalice el bit de código actual. Las funciones de devolución de llamada del nodo, como fs.readFile, agregarán eventos a la cola.
https://developer.mozilla.org/en

JavaScript normal no puede acceder directamente a la cola, pero en realidad no tiene que escribir módulos de nodo en JavaScript. El módulo fs y los otros módulos IO están escritos en C ++, lo que significa que pueden hacer cosas que el propio JavaScript no puede acceder al sistema de archivos y agregar cosas a la cola de eventos. Se llaman módulos nativos. Hay un buen tutorial sobre cómo escribir estos:
npm install -g goingnative

En algún momento llegué al mismo punto que tú. Simplemente comienza a jugar y eventualmente crea aplicaciones con JavaScript / Node.js sin conocer los conceptos subyacentes que ejecutan el lenguaje.

Node.js está construido sobre V8 (un intérprete de JavaScript de código abierto escrito por Google) siempre escuchamos sobre eso, pero nunca hablamos realmente sobre lo que está haciendo, fue una especie de caja negra para mí.

Recientemente me encontré con una gran conferencia celebrada por Philip Roberts en JSConf 2014 que realmente presenta bien el tema.

A los programadores de JavaScript les gusta usar palabras como “bucle de eventos”, “sin bloqueo”, “devolución de llamada”, “asíncrono”, “subproceso único” y “concurrencia”.
Decimos cosas como “no bloquee el bucle de eventos”, “asegúrese de que su código se ejecute a 60 cuadros por segundo”, “bueno, por supuesto, no funcionará, ¡esa función es una devolución de llamada asincrónica!”
Si eres como yo, asientes y estás de acuerdo, como si todo fuera obvio, aunque en realidad no sabes lo que significan las palabras; y, sin embargo, encontrar buenas explicaciones sobre cómo funciona realmente JavaScript no es tan fácil, ¡así que aprendamos!
Con algunas visualizaciones prácticas y trucos divertidos, obtengamos una comprensión intuitiva de lo que sucede cuando se ejecuta JavaScript.

Para readFile , está pasando una cadena (la ruta), otra cadena (la codificación) y una dirección de memoria de un fragmento de código . En realidad, es un poco más complicado que eso: también hay una dirección de memoria de un entorno en el que se debe ejecutar ese código, lo que le da el llamado “cierre”, pero eso es todo. El tiempo de ejecución lanzará una solicitud al sistema operativo para realizar E / S, y tomará nota de la dirección de la función que debe llamar cuando se complete esa solicitud en particular (lo que se indicará cuando el sistema operativo llame a algún código del tiempo de ejecución, de nuevo de forma asincrónica ) Por lo tanto, el tiempo de ejecución solo se traduce entre la interfaz nativa y lo que ofrece el idioma (en el caso anterior, la función de JavaScript llama).

Ninguna parte de la sintaxis proporciona esta información, readFile podría elegir ejecutar su función de forma inmediata y sincrónica, antes de regresar. Es solo una dirección de memoria de un fragmento de código (y un entorno asociado), lo que sucede depende de la función particular que está llamando (y probablemente se menciona en la documentación). Creo que la mayoría de las funciones en Node tienen versiones sincrónicas y asincrónicas, la única diferencia externamente visible es el nombre.