web-dev-qa-db-fra.com

Différence entre les fonctions asynchrones Javascript et les travailleurs Web?

En termes de threading, quelle est la différence entre les travailleurs Web et les fonctions déclarées comme 

async function xxx()
{
}

?

Je sais que les travailleurs Web sont exécutés sur des threads distincts, mais qu'en est-il des fonctions asynchrones? Ces fonctions sont-elles threadées de la même manière qu'une fonction exécutée via setInterval ou sont-elles soumises à un autre type de thread différent?

15
resle

Les fonctions asynchrones ne sont que du sucre syntaxique autour des promesses et sont des wrappers pour les rappels. Donc, fondamentalement, quand vous attendez quelque chose, le moteur JS continue avec d’autres choses jusqu’au rappel que vous attendez pour les appels. Qu'un autre thread soit impliqué dépend de ce que vous attendez dans la fonction async. S'il s'agit d'un timer (setTimeout), un timer interne est défini et le thread js continue avec d'autres tâches jusqu'à ce que le timer soit terminé, puis il continue l'exécution. Ce comportement est à peu près le même avec chaque fonction prenant un rappel ou retournant une promesse. Cependant, certains d'entre eux, en particulier dans l'environnement nodejs (fetch, fs.readFile) démarreront un autre thread en interne . Vous ne remettez que quelques arguments et recevez les résultats lorsque le fil est terminé. Avec les webworkers, vous contrôlez directement un autre thread. Pour sûr, vous pouvez également attendre les actions de cet autre thread:

 const workerDone = new Promise(res => window.onmessage = res);

(async function(){
   const result = await workerDone;
   //...
 })()

TLDR:

 JS  <---> callbacks / promises <--> internal Thread / Webworker
7
Jonas Wilms

Contrairement à WebWorkers, l'exécution des fonctions async n'est jamais garantie sur un thread distinct.

Ils ne bloquent pas le fil entier tant que leur réponse n’est pas arrivée. Vous pouvez penser qu’ils sont enregistrés en tant que en attente pour un résultat, laissez les autres codes s’exécuter et quand leur réponse est reçue, ils sont exécutés; d'où le nom asynchrone programmation.

Ceci est réalisé via une file de messages, qui est une liste de messages à traiter. Chaque message a une fonction associée qui est appelée afin de gérer le message.

Ce faisant:

setTimeout(() => {
  console.log('foo')
}, 1000)

ajoutera simplement la fonction de rappel (qui se connecte à la console) à la file de messages. Lorsque le délai de 1000 ms est écoulé, le message est extrait de la file de messages et exécuté.

Lorsque le chronomètre tourne, un autre code est libre d’exécuter. C'est ce qui donne l'illusion du multithreading.

L'exemple setTimeout ci-dessus utilise des rappels. Promises et async fonctionnent de la même manière à un niveau inférieur - ils se greffent sur ce concept de file d'attente de messages - mais ils sont simplement syntaxiquement différents.

6
Nik Kyriakides

Les fonctions asynchrones n'ont rien à voir avec les travailleurs Web ou les processus enfants de noeud. Contrairement à ces tâches, elles ne constituent pas une solution pour le traitement parallèle sur plusieurs threads.

Un async function est juste1 sucre syntaxique pour une fonction renvoyant une chaîne promesse then().

async function example() {
    await delay(1000);
    console.log("waited.");
}

est la même chose que

function example() {
    return Promise.resolve(delay(1000)).then(() => {
        console.log("waited.");
    });
}

Ces deux sont pratiquement indiscernables dans leur comportement. La sémantique de await ou une spécifiée en termes de promesses, et chaque async function renvoie une promesse pour son résultat.

1: Le sucre syntaxique obtient un bit plus élaboré en présence de structures de contrôle telles que if/else ou de boucles qui sont beaucoup plus difficiles à exprimer sous forme de chaîne de promesse linéaire, mais le concept reste identique.

Ces fonctions sont-elles liées de la même façon qu'une fonction exécutée via setInterval est?

Oui, les parties asynchrones de async functions sont exécutées en tant que rappels (promis) sur la boucle d'événements standard. La variable delay dans l'exemple ci-dessus serait implémentée avec la variable setTimeout normale - encapsulée dans une promesse de consommation facile:

function delay(t) {
    return new Promise(resolve => {
        setTimeout(resolve, t);
    });
}
5
Bergi

Les travailleurs sont également accessibles via un code asynchrone (c’est-à-dire des promesses). Toutefois, les travailleurs constituent une solution aux tâches gourmandes en ressources CPU qui bloqueraient le thread sur lequel le code JS est exécuté; même si cette fonction gourmande en ressources processeur est appelée de manière asynchrone.

Donc, si vous avez une fonction gourmande en ressources processeur telle que renderThread(duration) et si vous aimez

new Promise((v,x) => setTimeout(_ => (renderThread(500), v(1)),0)
    .then(v => console.log(v);
new Promise((v,x) => setTimeout(_ => (renderThread(100), v(2)),0)
    .then(v => console.log(v);

Même si le second prend moins de temps, il ne sera appelé que lorsque le premier aura libéré le thread du processeur. Nous aurons donc d'abord 1 et ensuite 2 sur la console.

Cependant, si ces deux fonctions avaient été exécutées sur des opérateurs distincts, le résultat escompté serait donc 2 et 1 car ils pourraient alors être exécutés simultanément, la seconde finissant et renvoyant un message plus tôt.

Ainsi, pour les opérations IO de base, le code asynchrone à un seul thread standard est très efficace et le besoin en travailleurs émane de la nécessité d’utiliser des tâches gourmandes en temps processeur qui peuvent être segmentées (affectées à plusieurs travailleurs à la fois), telles que FFT et autres.

3
Redu

Je veux ajouter ma propre réponse à ma question, avec la compréhension que j'ai recueillie à travers les réponses de toutes les autres personnes:

En fin de compte, tous les travailleurs sauf les travailleurs Web sont des rappels glorifiés. Le code dans les fonctions asynchrones, les fonctions appelées via des promesses, les fonctions appelées via setInterval, etc., est exécuté dans le thread principal avec un mécanisme proche du changement de contexte. Aucun parallélisme n'existe du tout. 

La véritable exécution en parallèle, avec tous ses avantages et ses pièges, ne concerne que les travailleurs Web.

(Dommage - je pensais qu'avec des "fonctions asynchrones" nous avions finalement rationalisé et "inline" threading)

1
resle

Voici un moyen d'appeler les fonctions standard en tant que travailleurs, permettant un vrai parallélisme. C'est un hack profane écrit dans le sang avec l'aide de satan, et il y a probablement une tonne de bizarreries dans les navigateurs qui peuvent le casser, mais pour autant que je sache, cela fonctionne.

[ contraintes : l'en-tête de la fonction doit être aussi simple que fonction f (a, b, c) et s'il y a un résultat, il doit passer par une instruction return]

function Async(func, params, callback)
{ 
 // ACQUIRE ORIGINAL FUNCTION'S CODE
 var text = func.toString(); 


 // EXTRACT ARGUMENTS
 var args = text.slice(text.indexOf("(") + 1, text.indexOf(")")); 
 args     = args.split(",");
 for(arg of args) arg = arg.trim();


 // ALTER FUNCTION'S CODE:
 // 1) DECLARE ARGUMENTS AS VARIABLES
 // 2) REPLACE RETURN STATEMENTS WITH THREAD POSTMESSAGE AND TERMINATION
 var body = text.slice(text.indexOf("{") + 1, text.lastIndexOf("}")); 
 for(var i = 0, c = params.length; i<c; i++) body = "var " + args[i] + " = " + JSON.stringify(params[i]) + ";" + body;
 body = body + " self.close();"; 
 body = body.replace(/return\s+([^;]*);/g, 'self.postMessage($1); self.close();');


 // CREATE THE WORKER FROM FUNCTION'S ALTERED CODE
 var code   = URL.createObjectURL(new Blob([body], {type:"text/javascript"}));
 var thread = new Worker(code);


 // WHEN THE WORKER SENDS BACK A RESULT, CALLBACK AND TERMINATE THE THREAD
 thread.onmessage =
 function(result)
 {
  if(callback) callback(result.data);

  thread.terminate();
 }

}

Donc, en supposant que vous ayez cette fonction potentiellement intensive en CPU ...

function HeavyWorkload(nx, ny) 
{
 var data = [];

 for(var x = 0; x < nx; x++)
 {
  data[x] = [];

  for(var y = 0; y < ny; y++)
  {
   data[x][y] = Math.random();
  }
 }

 return data;
}

... vous pouvez maintenant l'appeler comme ceci:

Async(HeavyWorkload, [1000, 1000],
function(result)
{
 console.log(result);
}
);
0
resle