Estoy tratando de entender las promesas y cómo funciona JavaScript con su cola y bucle de eventos, etc.

Pensé que si ponía una función síncrona lenta dentro de una promesa, esa función de sincronización lenta se delegaría en segundo plano y podría usar .then para tratarla cuando se hiciera.

function syncSleep(ms){
    var end = new Date().getTime() + ms;
    var start = new Date().getTime();

    while (start < end) {
      start = new Date().getTime();
    }
}

function p() {
  return new Promise(function(resolve) {
     syncSleep(5000);
     resolve("syncSleep done!");
  });
}

p().then( function(s) {
  var div = document.getElementById('async');
  div.innerHTML = s;
} );

var div = document.getElementById('sync');
div.innerHTML = "This should appear right away! (but it doesn't)";

https://jsfiddle.net/7mw6m2x5/

Sin embargo, la interfaz de usuario no responde mientras se ejecuta este código.

Entonces me preguntaba, ¿alguien puede explicar lo que está pasando aquí? ¿Son las promesas solo una forma de manejar el código que ya está "hecho para ser" asíncrono?

(Si es así, ¿cómo se hace eso?)

¿Cómo trato con el código de sincronización lenta cuando no quiero que congele la interfaz de usuario? ¿Tengo que usar un trabajador web para eso?

Agradecido por cualquier aclaración. Gracias.

3
john.abraham 7 may. 2016 a las 20:54

3 respuestas

La mejor respuesta

El código funciona como se esperaba.

Como Javascript tiene un solo subproceso, la interfaz de usuario se bloqueará mientras se ejecuta el bucle.

Las promesas son solo una buena manera de manejar el código asíncrono. Vea una introducción aquí:

http://www.html5rocks.com/en/tutorials/es6/promises/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Para poder mantener la respuesta de la interfaz de usuario mientras se ejecuta otro código en segundo plano, deberá usar Web Workers:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Citando de la página mencionada anteriormente:

"Los trabajadores web proporcionan un medio simple para que el contenido web ejecute scripts en subprocesos en segundo plano. El subproceso de trabajo puede realizar tareas sin interferir con la interfaz de usuario".

Actualización :

Después de sus comentarios, elaboré este pequeño script para demostrar la diferencia entre los enfoques de bloqueo y sin bloqueo. Hay una cierta repetición en el código, pero creo que es bastante simple de entender.

function setVal(s) {
  var divAsync = document.getElementById('async');
  var innerDiv = document.createElement('div');
  innerDiv.innerHTML = s + '<br>';
  divAsync.appendChild(innerDiv);
}


function syncSleep(ms) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  // This loop is blocking
  // The UI will only refresh after the loop completion
  while (now < end) {
    now = new Date().getTime();
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }
  }

}

function pBlock() {
  return new Promise(function(resolve) {
    syncSleep(3200);
    resolve("pBlock syncSleep done!");
  });
}


function noBlockUpdate(ms, resolve) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  function noBlock() {
    now = new Date().getTime();

    // paint every 1000ms;
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }

    if (now < end) {
      // NB: this is going to be called thousands of times
      // But the UI will still update every 1000 ms
      setTimeout(noBlock);
    } else {
      resolve("pNoBlock done!");
    }
  };
  noBlock();
}

function pNoBlock() {
  return new Promise(function(resolve) {
    noBlockUpdate(3200, resolve);
    setVal("pNoBlock launched!");
  });
}



pNoBlock().then(setVal);

var divSync = document.getElementById('sync');
divSync.innerHTML = "This appears right away!";



// Just wait 4 seconds so the non-blocking code completes
setTimeout(function() {
  // Clear all div's
  document.getElementById('sync').innerHTML = '';
  document.getElementById('async').innerHTML = '';

  var divSync = document.getElementById('sync');
  divSync.innerHTML = "This does not appear right away, only after the blocking operation is complete!";

  pBlock().then(setVal);
}, 4000);
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>

  <div id="sync"></div>

  <div id="async"></div>

</body>

</html>
4
VRPF 7 may. 2016 a las 22:22

Las promesas no son hilos. Son solo un azúcar para manejar eventos de éxito y falla (devoluciones de llamada) en código de subproceso único.

La devolución de llamada al constructor new Promise(cb) se ejecuta de forma inmediata y sincrónica. Las devoluciones de llamada dadas a .then(cb) / .catch(cb) se ejecutan en el siguiente tic después de que se resuelve / rechaza la promesa, pero también se ejecutan en el mismo hilo, y solo en él.

2
Kornel 7 may. 2016 a las 18:22

¿Las promesas son solo una forma de manejar el código que ya está 'hecho para ser' asíncrono?

Si. Las promesas no crean realmente operaciones asincrónicas. Su intención es facilitar el trabajo con operaciones asincrónicas, definiendo una API consistente. Pero no evitan el bloqueo por sí mismos más allá de un pequeño retraso entre cuando se invocan las devoluciones de llamada resolve() y .then().

La función proporcionada al constructor Promise se invoca de forma inmediata y sincrónica. Y, una vez que una devolución de llamada .then() comience a ejecutarse, tendrá que ejecutarse hasta su finalización antes de que el motor haga otra cosa.

¿Cómo trato con el código de sincronización lenta cuando no quiero que congele la IU?

Intente evitar las operaciones síncronas de larga duración tanto como sea posible. Una alternativa asincrónica a syncSleep() sería setTimeout().

function p() {
  return new Promise(function(resolve) {
    setTimeout(function () {
      resolve("syncSleep done!");
    }, 5000);
  });
}

Si no se puede evitar una operación síncrona de larga duración, entonces querrá intentar moverla a un proceso / instancia separado del motor, y, en los navegadores, eso se puede hacer con Web Workers.

Un posible término medio puede ser, si puede dividir la operación en varios pasos, separar cada paso por un breve setTimeout(). Esos descansos le darán al motor tiempo periódicamente para trabajar en otros eventos, incluida la actualización de la página para el usuario. Querrá que cada paso sea pequeño para tener tantos descansos como sea posible, ya que cada paso bloqueará todo lo demás una vez que comience.

3
Jonathan Lonowski 7 may. 2016 a las 19:25