Llamar a una función Javascript asincrónica de forma sincrónica

Resuelto Robert C. Barth asked hace 12 años • 15 respuestas

Primero, este es un caso muy específico de hacerlo de manera incorrecta a propósito para adaptar una llamada asíncrona a una base de código muy síncrona que tiene muchos miles de líneas de largo y el tiempo actualmente no permite realizar los cambios para "hacer bien." Me duele cada fibra de mi ser, pero la realidad y los ideales muchas veces no encajan. Sé que esto apesta.

Bien, eso fuera del camino, ¿cómo lo hago para poder:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

Todos los ejemplos (o la falta de ellos) utilizan bibliotecas y/o compiladores, los cuales no son viables para esta solución. Necesito un ejemplo concreto de cómo bloquearlo (por ejemplo, NO dejar la función hacer algo hasta que se llame a la devolución de llamada) SIN congelar la interfaz de usuario. Si tal cosa es posible en JS.

Robert C. Barth avatar Feb 03 '12 07:02 Robert C. Barth
Aceptado

"No me digas cómo debo hacerlo simplemente "de la manera correcta" o lo que sea"

DE ACUERDO. pero realmente deberías hacerlo de la manera correcta... o lo que sea

"Necesito un ejemplo concreto de cómo bloquearlo... SIN congelar la interfaz de usuario. Si tal cosa es posible en JS."

No, es imposible bloquear JavaScript en ejecución sin bloquear la interfaz de usuario.

Dada la falta de información, es difícil ofrecer una solución, pero una opción puede ser hacer que la función de llamada realice un sondeo para verificar una variable global y luego establecer la devolución de llamada dataen global.

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

Todo esto supone que puedes modificarlo doSomething(). No sé si eso está en las cartas.

Si se puede modificar, entonces no sé por qué no pasarías una devolución de llamada para doSomething()que te llamen desde la otra devolución de llamada, pero será mejor que me detenga antes de meterme en problemas. ;)


Oh, qué diablos. Diste un ejemplo que sugiere que se puede hacer correctamente, así que voy a mostrar esa solución...

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});

Debido a que su ejemplo incluye una devolución de llamada que se pasa a la llamada asíncrona, la forma correcta sería pasar una función para que doSomething()se invoque desde la devolución de llamada.

Por supuesto, si eso es lo único que hace la devolución de llamada, simplemente pasarías funcdirectamente...

myAsynchronousCall(param1, func);
 avatar Feb 03 '2012 00:02

Las funciones asíncronas , una característica de ES2017 , hacen que el código asíncrono parezca sincronizado mediante el uso de promesas (una forma particular de código asíncrono) y la awaitpalabra clave. Observe también en los ejemplos de código debajo de la palabra clave asyncdelante de la functionpalabra clave que significa una función async/await. La awaitpalabra clave no funcionará sin estar en una función prefijada con la asyncpalabra clave. Dado que actualmente no hay ninguna excepción a esto, eso significa que no funcionará ninguna espera de nivel superior (la espera de nivel superior significa una espera fuera de cualquier función). Aunque hay una propuesta para el nivel superiorawait .

ES2017 fue ratificado (es decir, finalizado) como estándar para JavaScript el 27 de junio de 2017. Es posible que Async await ya funcione en su navegador, pero si no, aún puede usar la funcionalidad usando un transpilador de JavaScript como babel o traceur . Chrome 55 tiene soporte total para funciones asíncronas. Entonces, si tiene un navegador más nuevo, puede probar el código a continuación.

Consulte la tabla de compatibilidad es2017 de kangax para conocer la compatibilidad del navegador.

Aquí hay un ejemplo de función de espera asíncrona llamada doAsyncque toma tres pausas de un segundo e imprime la diferencia horaria después de cada pausa desde la hora de inicio:

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();
Expandir fragmento

Cuando la palabra clave await se coloca antes de un valor de promesa (en este caso, el valor de promesa es el valor devuelto por la función doSomethingAsync), la palabra clave await pausará la ejecución de la llamada a la función, pero no pausará ninguna otra función y continuará. ejecutando otro código hasta que se resuelva la promesa. Después de que la promesa se resuelva, desenvolverá el valor de la promesa y puede pensar que la expresión de espera y promesa ahora está siendo reemplazada por ese valor desenvuelto.

Entonces, dado que await solo hace una pausa, espera y luego desenvuelve un valor antes de ejecutar el resto de la línea, puede usarlo en bucles for y llamadas a funciones internas como en el siguiente ejemplo que recopila las diferencias de tiempo esperadas en una matriz e imprime la matriz.

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})
Expandir fragmento

La función asíncrona en sí devuelve una promesa, por lo que puede usarla como una promesa con encadenamiento como lo hago arriba o dentro de otra función de espera asíncrona.

La función anterior esperaría cada respuesta antes de enviar otra solicitud. Si desea enviar las solicitudes simultáneamente, puede usar Promise.all .

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})
Expandir fragmento

Si la promesa posiblemente se rechaza, puede envolverla en un try catch u omitir el try catch y dejar que el error se propague a la llamada de captura de las funciones async/await. Debe tener cuidado de no dejar errores de promesa sin controlar, especialmente en Node.js. A continuación se muestran algunos ejemplos que muestran cómo funcionan los errores.

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)
Expandir fragmento

Si va aquí podrá ver las propuestas terminadas para las próximas versiones de ECMAScript.

Una alternativa a esto que se puede usar solo con ES2015 (ES6) es usar una función especial que envuelve una función de generador. Las funciones generadoras tienen una palabra clave de rendimiento que puede usarse para replicar la palabra clave de espera con una función circundante. La palabra clave de rendimiento y la función generadora tienen un propósito mucho más general y pueden hacer muchas más cosas que lo que hace la función async await. Si desea un contenedor de función de generador que pueda usarse para replicar async await , consultaría co.js. Por cierto, la función de co es muy similar a las funciones async await y devuelven una promesa. Honestamente, en este punto la compatibilidad del navegador es aproximadamente la misma tanto para las funciones generadoras como para las funciones asíncronas, por lo que si solo desea la funcionalidad de espera asíncrona, debe usar funciones asíncronas sin co.js. (Recomiendo simplemente usar async/await, es bastante compatible en la mayoría de los entornos en los que se admite el tachado anterior).

La compatibilidad del navegador es bastante buena ahora para las funciones Async (a partir de 2017) en todos los principales navegadores actuales (Chrome, Safari y Edge), excepto IE.

John avatar Nov 07 '2015 06:11 John

Eche un vistazo a las promesas de JQuery:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

Refactorizar el código:

    var dfd = nuevo jQuery.Deferred();


    función callBack(datos) {
       dfd.notify(datos);
    }

    // realiza la llamada asíncrona.
    myAsynchronousCall(param1, devolución de llamada);

    función hacer algo (datos) {
     // hacer cosas con datos...
    }

    $.cuando(dfd).luego(hacerAlgo);


Matt Taylor avatar Feb 03 '2012 00:02 Matt Taylor