Maison > interface Web > js tutoriel > Analyse des rappels et des modèles de conception de code dans Node.js programmation asynchrone_node.js

Analyse des rappels et des modèles de conception de code dans Node.js programmation asynchrone_node.js

WBOY
Libérer: 2016-05-16 15:15:13
original
1288 Les gens l'ont consulté

Le principal argument de vente de NodeJS - le mécanisme d'événements et les E/S asynchrones, ne sont pas transparents pour les développeurs. Les développeurs doivent écrire du code de manière asynchrone pour tirer parti de cet argument de vente, qui a été critiqué par certains opposants à NodeJS. Quoi qu’il en soit, la programmation asynchrone est effectivement la plus grande fonctionnalité de NodeJS. Sans maîtriser la programmation asynchrone, vous ne pouvez pas dire que vous avez vraiment appris NodeJS. Ce chapitre présentera diverses connaissances liées à la programmation asynchrone.

Dans le code, la manifestation directe de la programmation asynchrone sont les rappels. La programmation asynchrone repose sur des rappels, mais on ne peut pas dire que le programme devient asynchrone après avoir utilisé des rappels. Nous pouvons d’abord regarder le code suivant.

function heavyCompute(n, callback) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }

 callback(count);
}

heavyCompute(10000, function (count) {
 console.log(count);
});

console.log('hello');

Copier après la connexion
100000000
hello
Copier après la connexion

Comme vous pouvez le voir, la fonction de rappel dans le code ci-dessus est toujours exécutée avant le code suivant. JS lui-même s'exécute dans un seul thread, et il est impossible d'exécuter un autre code avant la fin de l'exécution d'un morceau de code, il n'y a donc pas de concept d'exécution asynchrone.

Cependant, si une fonction crée un autre thread ou processus, fait quelque chose en parallèle avec le thread principal JS et avertit le thread principal JS lorsque la chose est terminée, alors la situation est différente. Jetons un coup d'œil au code suivant.

setTimeout(function () {
 console.log('world');
}, 1000);

console.log('hello');

Copier après la connexion
hello
world
Copier après la connexion


Cette fois, vous pouvez voir que la fonction de rappel est exécutée après le code suivant. Comme mentionné ci-dessus, JS lui-même est monothread et ne peut pas être exécuté de manière asynchrone. Par conséquent, nous pouvons penser que les fonctions spéciales fournies par l'environnement d'exécution en dehors des spécifications JS telles que setTimeout sont de créer un thread parallèle et de revenir immédiatement, permettant ainsi. Maître JS vers Le processus peut ensuite exécuter le code suivant et exécuter la fonction de rappel après avoir reçu une notification du processus parallèle. En plus des fonctions courantes telles que setTimeout et setInterval, ces fonctions incluent également des API asynchrones fournies par NodeJS telles que fs.readFile.

De plus, nous revenons toujours au fait que JS s'exécute dans un seul thread, ce qui détermine que JS ne peut pas exécuter d'autres codes, y compris des fonctions de rappel, avant d'exécuter un morceau de code. En d'autres termes, même si le thread parallèle termine son travail et demande au thread principal JS d'exécuter la fonction de rappel, la fonction de rappel ne démarrera pas l'exécution tant que le thread principal JS ne sera pas inactif. Ce qui suit est un tel exemple.

function heavyCompute(n) {
 var count = 0,
  i, j;

 for (i = n; i > 0; --i) {
  for (j = n; j > 0; --j) {
   count += 1;
  }
 }
}

var t = new Date();

setTimeout(function () {
 console.log(new Date() - t);
}, 1000);

heavyCompute(50000);

Copier après la connexion
8520
Copier après la connexion


Comme vous pouvez le constater, le temps d'exécution réel de la fonction de rappel qui était censée être appelée après 1 seconde a été considérablement retardé car le thread principal JS était occupé à exécuter un autre code.

Modèles de conception de code
La programmation asynchrone comporte de nombreux modèles de conception de code uniques. Afin d'obtenir la même fonction, le code écrit en mode synchrone et en mode asynchrone sera très différent. Certains modèles courants sont présentés ci-dessous.

Valeur de retour de la fonction
Il est très courant d'utiliser la sortie d'une fonction comme entrée d'une autre fonction. En mode synchrone, le code s'écrit généralement comme suit :

var output = fn1(fn2('input'));
// Do something.
Copier après la connexion

En mode asynchrone, puisque le résultat de l'exécution de la fonction n'est pas transmis via la valeur de retour, mais via la fonction de rappel, le code est généralement écrit de la manière suivante :

fn2('input', function (output2) {
 fn1(output2, function (output1) {
  // Do something.
 });
});
Copier après la connexion

Comme vous pouvez le voir, cette méthode est une fonction de rappel imbriquée dans une seule fonction de rappel. S'il y en a trop, il est facile d'écrire du code en forme de >.

Parcourir le tableau
Lors du parcours d'un tableau, il est également courant d'utiliser une fonction pour effectuer un traitement sur les données membres en séquence. Si la fonction est exécutée de manière synchrone, le code suivant sera généralement écrit :

var len = arr.length,
 i = 0;

for (; i < len; ++i) {
 arr[i] = sync(arr[i]);
}

// All array items have processed.

Copier après la connexion

Si la fonction est exécutée de manière asynchrone, le code ci-dessus ne peut pas garantir que tous les membres du tableau ont été traités après la fin de la boucle. Si les membres du tableau doivent être traités en série les uns après les autres, le code asynchrone est généralement écrit comme suit :

(function next(i, len, callback) {
 if (i < len) {
  async(arr[i], function (value) {
   arr[i] = value;
   next(i + 1, len, callback);
  });
 } else {
  callback();
 }
}(0, arr.length, function () {
 // All array items have processed.
}));
Copier après la connexion

Comme vous pouvez le voir, le code ci-dessus ne transmet que le membre suivant du tableau et démarre le prochain cycle d'exécution après que la fonction asynchrone soit exécutée une fois et renvoie le résultat de l'exécution jusqu'à ce que tous les membres du tableau soient traités, l'exécution du code suivant. est déclenché par des rappels.

Si les membres du tableau peuvent être traités en parallèle, mais que le code ultérieur nécessite toujours que tous les membres du tableau soient traités avant de pouvoir être exécutés, le code asynchrone sera ajusté sous la forme suivante :

(function (i, len, count, callback) {
 for (; i < len; ++i) {
  (function (i) {
   async(arr[i], function (value) {
    arr[i] = value;
    if (++count === len) {
     callback();
    }
   });
  }(i));
 }
}(0, arr.length, 0, function () {
 // All array items have processed.
}));
Copier après la connexion

Comme vous pouvez le voir, par rapport à la version de parcours série asynchrone, le code ci-dessus traite tous les membres du tableau en parallèle et utilise la variable counter pour déterminer quand tous les membres du tableau ont été traités.

Gestion des exceptions
Le mécanisme de capture et de gestion des exceptions fourni par JS lui-même - try..catch.., ne peut être utilisé que pour du code exécuté de manière synchrone. Ci-dessous un exemple.

function sync(fn) {
 return fn();
}

try {
 sync(null);
 // Do something.
} catch (err) {
 console.log('Error: %s', err.message);
}

Copier après la connexion
Error: object is not a function
Copier après la connexion
Copier après la connexion

Comme vous pouvez le voir, l'exception bouillonnera le long du chemin d'exécution du code jusqu'à ce qu'elle soit interceptée lorsqu'elle rencontrera la première instruction try. Cependant, étant donné que les fonctions asynchrones interrompent le chemin d'exécution du code, lorsque les exceptions générées pendant et après l'exécution de la fonction asynchrone remontent jusqu'à l'emplacement où le chemin d'exécution est interrompu, si aucune instruction try n'est rencontrée, elles seront levées comme une exception globale. . Ci-dessous un exemple.

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  callback(fn());
 }, 0);
}

try {
 async(null, function (data) {
  // Do something.
 });
} catch (err) {
 console.log('Error: %s', err.message);
}

Copier après la connexion
/home/user/test.js:4
  callback(fn());
     ^
TypeError: object is not a function
 at null._onTimeout (/home/user/test.js:4:13)
 at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
Copier après la connexion

因为代码执行路径被打断了,我们就需要在异常冒泡到断点之前用 try 语句把异常捕获住,并通过回调函数传递被捕获的异常。于是我们可以像下边这样改造上边的例子。

function async(fn, callback) {
 // Code execution path breaks here.
 setTimeout(function () {
  try {
   callback(null, fn());
  } catch (err) {
   callback(err);
  }
 }, 0);
}

async(null, function (err, data) {
 if (err) {
  console.log('Error: %s', err.message);
 } else {
  // Do something.
 }
});

Copier après la connexion
Error: object is not a function
Copier après la connexion
Copier après la connexion

可以看到,异常再次被捕获住了。在 NodeJS 中,几乎所有异步 API 都按照以上方式设计,回调函数中第一个参数都是 err。因此我们在编写自己的异步函数时,也可以按照这种方式来处理异常,与 NodeJS 的设计风格保持一致。

有了异常处理方式后,我们接着可以想一想一般我们是怎么写代码的。基本上,我们的代码都是做一些事情,然后调用一个函数,然后再做一些事情,然后再调用一个函数,如此循环。如果我们写的是同步代码,只需要在代码入口点写一个 try 语句就能捕获所有冒泡上来的异常,示例如下。

function main() {
 // Do something.
 syncA();
 // Do something.
 syncB();
 // Do something.
 syncC();
}

try {
 main();
} catch (err) {
 // Deal with exception.
}

Copier après la connexion

但是,如果我们写的是异步代码,就只有呵呵了。由于每次异步函数调用都会打断代码执行路径,只能通过回调函数来传递异常,于是我们就需要在每个回调函数里判断是否有异常发生,于是只用三次异步函数调用,就会产生下边这种代码。

function main(callback) {
 // Do something.
 asyncA(function (err, data) {
  if (err) {
   callback(err);
  } else {
   // Do something
   asyncB(function (err, data) {
    if (err) {
     callback(err);
    } else {
     // Do something
     asyncC(function (err, data) {
      if (err) {
       callback(err);
      } else {
       // Do something
       callback(null);
      }
     });
    }
   });
  }
 });
}

main(function (err) {
 if (err) {
  // Deal with exception.
 }
});

Copier après la connexion

可以看到,回调函数已经让代码变得复杂了,而异步方式下对异常的处理更加剧了代码的复杂度。

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal