¿Cuándo es JavaScript sincrónico?

202

He tenido la impresión de que JavaScript siempre fue asíncrono. Sin embargo, he aprendido que hay situaciones en las que no es así (es decir, manipulaciones DOM). ¿Hay alguna buena referencia sobre cuándo será síncrono y cuándo será asíncrono? ¿JQuery afecta esto en absoluto?

Brian
fuente
14
Siempre con la excepción de ajax.
defecto el
La respuesta aceptada es incorrecta y engañosa, compruébalo amablemente.
Suraj Jain
2
También fue útil ver youtube.com/watch?v=8aGhZQkoFbQ para comprender el bucle de eventos y cómo funcionan la pila, las API web y la cola de tareas con respecto a la sincronización y la sincronización
mtpultz
1
@ defau1t ¿No está mal? JavaScript siempre está sincronizado, cuando finaliza la llamada ajax, la devolución de llamada termina en la cola, ¿cómo es una excepción a la naturaleza síncrona del script java?
Suraj Jain

Respuestas:

281

JavaScript siempre es síncrono y de subproceso único. Si está ejecutando un bloque de código JavaScript en una página, entonces no se ejecutará ningún otro JavaScript en esa página.

JavaScript solo es asíncrono en el sentido de que puede hacer, por ejemplo, llamadas Ajax. La llamada Ajax dejará de ejecutarse y otros códigos podrán ejecutarse hasta que la llamada regrese (con éxito o no), momento en el que la devolución de llamada se ejecutará de forma sincrónica. Ningún otro código se ejecutará en este momento. No interrumpirá ningún otro código que se esté ejecutando actualmente.

Los temporizadores de JavaScript funcionan con este mismo tipo de devolución de llamada.

Describir JavaScript como asíncrono es quizás engañoso. Es más exacto decir que JavaScript es sincrónico y de un solo subproceso con varios mecanismos de devolución de llamada.

jQuery tiene una opción en llamadas Ajax para hacerlas sincrónicamente (con la async: falseopción). Los principiantes pueden verse tentados a usar esto incorrectamente porque permite un modelo de programación más tradicional al que uno podría estar más acostumbrado. La razón por la que es problemático es que esta opción bloqueará todo JavaScript en la página hasta que finalice, incluidos todos los controladores de eventos y temporizadores.

cletus
fuente
31
lo siento, no entendí bien esta afirmación "El código dejará de ejecutarse hasta que la llamada regrese (exitosamente o por error)". ¿Podrías dar más detalles? ¿Cómo puede ser cierta esa afirmación cuando también dice "No interrumpirá ningún otro código que se esté ejecutando"; ¿Estás hablando del código de devolución de llamada solo en la primera declaración? Por favor iluminame.
Krishna
2
Nettuts tiene un tutorial que es bastante bueno para explicar los conceptos básicos de async aquí: net.tutsplus.com/tutorials/javascript-ajax/…
RobW
26
@cletus La declaración "El código dejará de ejecutarse hasta que la llamada regrese" necesita corrección porque la ejecución no se detiene. La ejecución del código puede continuar. De lo contrario, significaría que la llamada es sincrónica.
HS.
1
No entendí esa declaración también.
remolque
12
Esta respuesta es increíblemente engañosa y confusa. Por favor, consulte la respuesta de CMS o Faraz Ahmad en su lugar.
iono
214

JavaScript es de un solo subproceso y tiene un modelo de ejecución síncrono. Un solo subproceso significa que se está ejecutando un comando a la vez. Sincrónico significa uno a la vez, es decir, se está ejecutando una línea de código a la vez en el orden en que aparece el código. Entonces, en JavaScript, una cosa está sucediendo a la vez.

Contexto de ejecución

El motor de JavaScript interactúa con otros motores en el navegador. En la pila de ejecución de JavaScript hay un contexto global en la parte inferior y luego, cuando invocamos funciones, el motor de JavaScript crea nuevos contextos de ejecución para las funciones respectivas. Cuando la función llamada sale, su contexto de ejecución aparece de la pila, y luego aparece el siguiente contexto de ejecución, etc.

Por ejemplo

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

En el código anterior se creará un contexto de ejecución global y en este contexto var onese almacenará y su valor será 1 ... cuando se invoque la invocación xyz (), se creará un nuevo contexto de ejecución y si hubiéramos definido alguna variable en la función xyz, esas variables se almacenarían en el contexto de ejecución de xyz (). En la función xyz invocamos abc () y luego se crea el contexto de ejecución abc () y se coloca en la pila de ejecución ... Ahora, cuando abc () termina, su contexto se extrae de la pila, entonces el contexto xyz () se extrae de apilar y luego aparecerá el contexto global ...

Ahora sobre devoluciones de llamada asincrónicas; asíncrono significa más de uno a la vez.

Al igual que la pila de ejecución, está la Cola de eventos . Cuando queremos que se nos notifique sobre algún evento en el motor de JavaScript, podemos escuchar ese evento, y ese evento se coloca en la cola. Por ejemplo, un evento de solicitud Ajax o un evento de solicitud HTTP.

Siempre que la pila de ejecución esté vacía, como se muestra en el ejemplo de código anterior, el motor de JavaScript revisa periódicamente la cola de eventos y ve si hay algún evento sobre el que se deba notificar. Por ejemplo, en la cola hubo dos eventos, una solicitud ajax y una solicitud HTTP. También busca ver si hay una función que deba ejecutarse en ese desencadenador de eventos ... Entonces, el motor de JavaScript recibe una notificación sobre el evento y conoce la función respectiva que se ejecutará en ese evento ... Entonces, el motor de JavaScript invoca el función de controlador, en el caso de ejemplo, por ejemplo, se invocará AjaxHandler () y, como siempre cuando se invoca una función, su contexto de ejecución se coloca en el contexto de ejecución y ahora la ejecución de la función finaliza y la solicitud de evento ajax también se elimina de la cola de eventos ... Cuando AjaxHandler () finaliza, la pila de ejecución está vacía, por lo que el motor vuelve a mirar la cola de eventos y ejecuta la función de controlador de eventos de la solicitud HTTP que fue la siguiente en la cola. Es importante recordar que la cola de eventos se procesa solo cuando la pila de ejecución está vacía.

Por ejemplo, vea el siguiente código que explica la pila de ejecución y el manejo de la cola de eventos mediante el motor Javascript.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

Y

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Ahora ejecute la página web y haga clic en la página, y vea el resultado en la consola. La salida será

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

El motor de JavaScript ejecuta el código de forma síncrona como se explica en la parte del contexto de ejecución, el navegador pone las cosas en la cola de eventos de forma asíncrona. Por lo tanto, las funciones que tardan mucho tiempo en completarse pueden interrumpir el manejo de eventos. Las cosas que suceden en un navegador, como los eventos, son manejadas de esta manera por JavaScript, si se supone que un oyente debe ejecutarse, el motor lo ejecutará cuando la pila de ejecución esté vacía. Y los eventos se procesan en el orden en que ocurren, por lo que la parte asincrónica se trata de lo que sucede fuera del motor, es decir, qué debe hacer el motor cuando ocurren esos eventos externos.

Entonces JavaScript es siempre sincrónico.


fuente
16
Esta respuesta es muy clara, debería recibir más votos a favor.
Ranu
77
Ciertamente, la mejor explicación para el comportamiento asincrónico de Javascript que he leído.
Charles Jaimet
1
Buena explicación del contexto de ejecución y la cola.
Divyanshu Maithani
1
Por supuesto, esto requiere que leas un poco sobre la pila de contexto de ejecución, y solo la adición de que está vacía y que event event me hace sentir que finalmente entiendo la excitación de script Java de manera determinista. Lo que es peor es que siento que solo toma una página de lectura, pero apenas lo encuentro en ninguna parte. Entonces, ¿por qué nadie lo dice? ¿O no saben o qué? Pero siento que si un tutorial de js tuviera esto, podría haberme ahorrado mucho tiempo. >: |
Mariscal artesanal
2
Explicación perfecta!
Julsy
100

JavaScript es de un solo subproceso y todo el tiempo trabaja en una ejecución normal de flujo de código síncrono.

Un buen ejemplo del comportamiento asíncrono que puede tener JavaScript son los eventos (interacción del usuario, resultados de la solicitud de Ajax, etc.) y los temporizadores, básicamente acciones que pueden ocurrir en cualquier momento.

Te recomendaría que le eches un vistazo al siguiente artículo:

Ese artículo lo ayudará a comprender la naturaleza de un solo subproceso de JavaScript y cómo los temporizadores funcionan internamente y cómo funciona la ejecución asíncrona de JavaScript.

asíncrono

CMS
fuente
La respuesta aceptada engaña ¿podemos hacer algo en ese caso? /
Suraj Jain
8

Para alguien que realmente entiende cómo funciona JS, esta pregunta puede parecer desagradable, sin embargo, la mayoría de las personas que usan JS no tienen un nivel de conocimiento tan profundo (y no necesariamente lo necesitan) y para ellos este es un punto bastante confuso, lo haré intenta responder desde esa perspectiva.

JS es síncrono en la forma en que se ejecuta su código. cada línea solo se ejecuta después de la línea antes de que se haya completado y si esa línea llama a una función después de que se complete ect ...

El principal punto de confusión surge del hecho de que su navegador puede decirle a JS que ejecute más código en cualquier momento (similar a cómo puede ejecutar más código JS en una página desde la consola). Como ejemplo, JS tiene funciones de devolución de llamada cuyo propósito es permitir que JS SE COMPORTE de forma asíncrona para que otras partes de JS puedan ejecutarse mientras esperan que una función JS que se haya ejecutado (es decir, una GETllamada) devuelva una respuesta, JS continuará ejecutándose hasta el navegador tiene una respuesta en ese punto, el bucle de eventos (navegador) ejecutará el código JS que llama a la función de devolución de llamada.

Dado que el bucle de eventos (navegador) puede ingresar más JS para ejecutarse en cualquier punto en ese sentido, JS es asíncrono (las cosas principales que harán que un navegador ingrese código JS son tiempos de espera, devoluciones de llamada y eventos)

Espero que esto sea lo suficientemente claro como para ser útil para alguien.

Yehuda Schwartz
fuente
4

Definición

El término "asíncrono" puede usarse en significados ligeramente diferentes, lo que resulta en respuestas aparentemente conflictivas aquí, mientras que en realidad no lo son. Wikipedia en Asynchrony tiene esta definición:

La asincronía, en la programación de computadoras, se refiere a la ocurrencia de eventos independientes del flujo principal del programa y las formas de lidiar con tales eventos. Estos pueden ser eventos "externos", como la llegada de señales, o acciones instigadas por un programa que tienen lugar simultáneamente con la ejecución del programa, sin que el programa se bloquee para esperar los resultados.

el código que no es JavaScript puede poner en cola tales eventos "externos" en algunas de las colas de eventos de JavaScript. Pero eso es lo más lejos que llega.

Sin preempresión

No hay interrupción externa al ejecutar código JavaScript para ejecutar algún otro código JavaScript en su script. Las piezas de JavaScript se ejecutan una tras otra, y el orden está determinado por el orden de los eventos en cada cola de eventos y la prioridad de esas colas.

Por ejemplo, puede estar absolutamente seguro de que ningún otro JavaScript (en el mismo script) se ejecutará mientras se ejecuta el siguiente código:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

En otras palabras, no hay preferencia en JavaScript. Sea lo que sea que esté en las colas de eventos, el procesamiento de esos eventos tendrá que esperar hasta que dicho código se haya completado. La especificación EcmaScript dice en la sección 8.4 Trabajos y colas de trabajos :

La ejecución de un trabajo solo se puede iniciar cuando no hay un contexto de ejecución en ejecución y la pila del contexto de ejecución está vacía.

Ejemplos de asincronía

Como otros ya han escrito, hay varias situaciones en las que la asincronía entra en juego en JavaScript, y siempre involucra una cola de eventos, que solo puede resultar en la ejecución de JavaScript cuando no se ejecuta otro código JavaScript:

  • setTimeout(): el agente (por ejemplo, el navegador) colocará un evento en una cola de eventos cuando haya expirado el tiempo de espera. La supervisión del tiempo y la ubicación del evento en la cola se realiza mediante código que no es JavaScript, por lo que puede imaginar que esto sucede en paralelo con la ejecución potencial de algún código JavaScript. Pero la devolución de llamada proporcionada a setTimeoutsolo se puede ejecutar cuando el código JavaScript que se está ejecutando actualmente se ha completado y se está leyendo la cola de eventos adecuada.

  • fetch(): el agente utilizará las funciones del sistema operativo para realizar una solicitud HTTP y monitorear cualquier respuesta entrante. Nuevamente, esta tarea que no es JavaScript puede ejecutarse en paralelo con algún código JavaScript que aún se está ejecutando. Pero el procedimiento de resolución de la promesa, que resolverá la promesa devuelta por fetch(), solo puede ejecutarse cuando el JavaScript que se está ejecutando actualmente se ha completado.

  • requestAnimationFrame(): el motor de representación del navegador (que no sea JavaScript) colocará un evento en la cola de JavaScript cuando esté listo para realizar una operación de pintura. Cuando se procesa el evento de JavaScript, se ejecuta la función de devolución de llamada.

  • queueMicrotask(): coloca inmediatamente un evento en la cola de microtask. La devolución de llamada se ejecutará cuando la pila de llamadas esté vacía y ese evento se consuma.

Hay muchos más ejemplos, pero todas estas funciones son proporcionadas por el entorno host, no por el núcleo de EcmaScript. Con el núcleo de EcmaScript, puede colocar sincrónicamente un evento en una cola de trabajos de Promise con Promise.resolve().

Construcciones de lenguaje

EcmaScript proporciona varias construcciones de lenguaje para apoyar el patrón asincronía, tales como yield, async, await. Pero no se equivoque: ningún código externo será interrumpido por un evento externo. La "interrupción" que yieldy awaitparece ofrecer es sólo una manera controlada, predefinido de regresar de una llamada de función y la restauración de su contexto de ejecución más adelante, ya sea por código JS (en el caso de yield), o la cola de eventos (en el caso de await)

Manejo de eventos DOM

Cuando el código JavaScript accede a la API DOM, esto puede en algunos casos hacer que la API DOM active una o más notificaciones sincrónicas. Y si su código tiene un controlador de eventos escuchando eso, se llamará.

Esto puede parecer una concurrencia preventiva, pero no es así: una vez que su (s) controlador (es) de eventos devuelve (n), la API DOM eventualmente también regresará y el código JavaScript original continuará.

En otros casos, la API DOM solo enviará un evento en la cola de eventos apropiada, y JavaScript lo recogerá una vez que la pila de llamadas se haya vaciado.

Ver eventos sincrónicos y asincrónicos

trincot
fuente
0

Es sincrónico en todos los casos.

Ejemplo de bloqueo de hilo con Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

El resultado será:

Test start...
Test finish...
Finish!
Eduardo Cuomo
fuente