web-dev-qa-db-fra.com

Appeler une fonction Javascript asynchrone de manière synchrone

Tout d’abord, c’est un cas très particulier de mauvaise utilisation intentionnelle de la modification d’un appel asynchrone dans une base de code très synchrone comportant plusieurs milliers de lignes et dont le temps ne permet pas pour le moment de faire les modifications nécessaires. c'est juste. " Cela fait mal à chaque fibre de mon être, mais la réalité et les idéaux ne font souvent pas bon ménage. Je sais que ça craint.

OK, à part ça, comment faire pour que je puisse:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

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

Les exemples (ou l'absence de ceux-ci) utilisent tous des bibliothèques et/ou des compilateurs, qui ne sont pas viables pour cette solution. J'ai besoin d'un exemple concret sur la façon de le bloquer (par exemple, NE PAS laisser la fonction doSomething avant que le rappel ne soit appelé) SANS geler l'interface utilisateur. Si une telle chose est possible dans JS.

190
Robert C. Barth

"ne me dites pas comment je devrais le faire" de la bonne façon "ou peu importe"

D'ACCORD. mais vous devriez vraiment le faire de la bonne façon ... ou peu importe

"J'ai besoin d'un exemple concret sur la façon de le bloquer ... SANS bloquer l'interface utilisateur. Si une telle chose est possible dans JS."

Non, il est impossible de bloquer le JavaScript en cours d'exécution sans bloquer l'interface utilisateur.

Étant donné le manque d'informations, il est difficile de proposer une solution, mais une option peut être de demander à la fonction d'appel d'effectuer une interrogation pour vérifier une variable globale, puis de définir le callback data sur la variable globale.

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);

Tout cela suppose que vous puissiez modifier doSomething(). Je ne sais pas si c'est dans les cartes.

Si elle peut être modifiée, je ne vois pas pourquoi vous ne feriez pas simplement un rappel à doSomething() pour être appelée à partir de l'autre rappel, mais je ferais mieux de m'arrêter avant d'avoir des problèmes. ;)


Oh, que diable. Vous avez donné un exemple qui suggère que cela peut être fait correctement, alors je vais montrer cette solution ...

function doSomething( func ) {

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

  myAsynchronousCall(param1, callBack);

}

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

Étant donné que votre exemple inclut un rappel transmis à l'appel asynchrone, la méthode la plus appropriée consiste à transmettre une fonction à doSomething() afin qu'elle soit appelée à partir du rappel.

Bien sûr, si c'est la seule chose que fait le rappel, vous ne feriez que passer func directement ...

myAsynchronousCall(param1, func);
122
user1106925

Jetez un coup d'œil aux promesses JQuery:

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

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

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

Refactoriser le code:

 
 var dfd = new jQuery.Deferred (); 
 
 
 callBack (données) {
 dfd.notify ( data); 
} 
 
 // faire l'appel asynchrone. 
 myAsynchronousCall (param1, callBack); 
 
 function doQuelque chose (data) {
 // créer des données avec des données ... 
} 
 
 $ .when (dfd) .then (doSomething); 
 
 
48
Matt Taylor

Fonctions asynchrones , une fonctionnalité dans ES2017 , permet au code asynchrone de se synchroniser à l'aide de promesses (une forme particulière de code async) et de la variable await mot-clé. Notez également dans les exemples de code situés sous le mot-clé async devant le mot-clé function qui signifie une fonction asynchrone/wait. Le mot clé await ne fonctionnera pas sans une fonction prédéfinie avec le mot clé async. Comme il n’ya actuellement aucune exception à cette règle, cela signifie qu’aucun niveau d’attente supérieur ne fonctionnera (le niveau supérieur attend, ce qui signifie une attente en dehors de toute fonction). Bien qu'il y ait un proposition pour le niveau supérieur await .

ES2017 a été ratifié (c'est-à-dire finalisé) comme norme pour JavaScript le 27 juin 2017. Async wait peut déjà fonctionner dans votre navigateur, mais si vous ne l'êtes pas, vous pouvez toujours utiliser la fonctionnalité utilisant un transpiler javascript comme babel ou traceur . Chrome 55 prend totalement en charge les fonctions asynchrones. Donc, si vous avez un navigateur plus récent, vous pourrez peut-être essayer le code ci-dessous.

Voir tableau de compatibilité es2017 de kangax pour la compatibilité du navigateur.

Voici un exemple de fonction d'attente asynchrone appelée doAsync qui prend trois pauses d'une seconde et affiche la différence de temps après chaque pause à partir de l'heure de début:

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();

Lorsque le mot-clé wait est placé avant une valeur de promesse (dans ce cas, la valeur de promesse est la valeur renvoyée par la fonction doSomethingAsync), le mot-clé wait suspendra l'exécution de l'appel de la fonction, mais ne mettra pas en pause les autres fonctions et continuera. exécuter un autre code jusqu'à ce que la promesse soit résolue. Une fois que la promesse a été résolue, la valeur de la promesse sera déballée et vous pourrez penser à l’attente et à l’expression de la promesse comme étant remplacée par cette valeur non enveloppée.

Donc, puisque wait juste pauses attend, puis décompresse une valeur avant d'exécuter le reste de la ligne, vous pouvez l'utiliser pour les boucles et les appels de fonction internes, comme dans l'exemple ci-dessous, qui collecte les différences de temps attendues dans un tableau et les affiche.

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)
})

La fonction asynchrone elle-même renvoie une promesse afin que vous puissiez l'utiliser comme une promesse avec chaînage, comme je le fais ci-dessus ou dans une autre fonction async.

La fonction ci-dessus attendrait chaque réponse avant d'envoyer une autre demande si vous souhaitez envoyer les demandes simultanément, vous pouvez utiliser 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)
})

Si la promesse rejette éventuellement, vous pouvez l'envelopper dans une capture d'essai ou ignorer la capture d'essai et laisser l'erreur se propager à l'appel intercepté des fonctions async/wait. Vous devez faire attention à ne pas laisser les erreurs de promesses non gérées, en particulier dans Node.js. Vous trouverez ci-dessous quelques exemples illustrant le fonctionnement des erreurs.

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)

Si vous y allez ici vous pourrez voir les propositions finies pour les prochaines versions d'ECMAScript.

Une alternative à cela qui peut être utilisée uniquement avec ES2015 (ES6) consiste à utiliser une fonction spéciale qui encapsule une fonction de générateur. Les fonctions de générateur ont un mot-clé de rendement qui peut être utilisé pour répliquer le mot-clé wait avec une fonction environnante. Le mot-clé de rendement et la fonction de générateur sont beaucoup plus généraux et peuvent faire beaucoup plus de choses que la fonction async en attente. Si vous voulez un wrapper de fonction de générateur pouvant être utilisé pour répliquer en attente, attendez, je jetterai un œil à co.js . À propos, les fonctions de co ressemblent beaucoup à celles d’attente asynchrone qui renvoient une promesse. Honnêtement, à ce stade, la compatibilité du navigateur est à peu près la même pour les fonctions du générateur et pour les fonctions asynchrones. Si vous souhaitez simplement utiliser la fonctionnalité async en attente, vous devez utiliser les fonctions asynchrones sans co.js.

La prise en charge des navigateurs est en fait plutôt bonne maintenant pour les fonctions asynchrones (à partir de 2017) dans tous les principaux navigateurs actuels (Chrome, Safari et Edge), sauf IE.

46
John

Il existe une solution de contournement de Nice à http://taskjs.org/

Il utilise des générateurs qui sont nouveaux pour javascript. Donc, actuellement, il n'est pas implémenté par la plupart des navigateurs. Je l’ai testé sous firefox, et pour moi c’est un bon moyen d’envelopper une fonction asynchrone.

Voici un exemple de code du projet GitHub

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}
6
George Vinokhodov

Vous pouvez forcer JavaScript asynchrone dans NodeJS à être synchrone avec sync-rpc .

Cela va définitivement geler votre interface utilisateur, donc je ne suis pas du tout en mesure de déterminer s'il est possible de prendre le raccourci dont vous avez besoin. Il n'est pas possible de suspendre le fil One And Only en JavaScript, même si NodeJS vous permet parfois de le bloquer. Aucun rappel, aucun événement, rien d’asynchrone ne pourra être traité jusqu’à ce que votre promesse soit résolue. Donc, à moins que le lecteur ait une situation inévitable comme le PO (ou, dans mon cas, écrit un script Shell glorifié sans rappel, aucun événement, etc.), NE FAITES PAS CELA!

Mais voici comment vous pouvez le faire:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

LIMITES:

Celles-ci sont à la fois une conséquence de la façon dont sync-rpc est implémenté, ce qui revient à abuser de require('child_process').spawnSync:

  1. Cela ne fonctionnera pas dans le navigateur.
  2. Les arguments de votre fonction doivent être sérialisables. Vos arguments vont entrer et sortir de JSON.stringify, ainsi les fonctions et les propriétés non énumérables comme les chaînes de prototypes seront perdues.
1
meustrus