Maison  >  Article  >  interface Web  >  ES6 async+wait solution synchrone/asynchrone

ES6 async+wait solution synchrone/asynchrone

小云云
小云云original
2018-02-05 14:17:481647parcourir

Cet article présente principalement l'explication détaillée de la solution de synchronisation/asynchrone async+await d'ES6. Cet article utilise la manière la plus concise de débloquer async + wait. Ceux qui sont intéressés peuvent en savoir plus

La programmation asynchrone a toujours été. La programmation JavaScript est devenue une question majeure. Concernant les solutions asynchrones, ES6 a d'abord vu émerger Promise basée sur la gestion d'état, puis la fonction Generator + co function, et enfin la solution ES7 async + wait.

Cet article s'efforce de débloquer async + wait de la manière la plus concise.

Plusieurs scénarios de programmation asynchrone

Commencez par une question courante : Comment imprimer la séquence d'itérations de manière asynchrone dans une boucle for ?

Nous pouvons facilement penser à utiliser des fermetures ou la portée au niveau du bloc let spécifiée dans ES6 pour répondre à cette question.


for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val),100);
}
// => 预期结果依次为:1, 2, 3, 4

Ce qui est décrit ici est une synchronisation qui se produit de manière uniforme, et ils sont mis en file d'attente dans la file d'attente asynchrone dans un ordre prédéterminé en attente d'exécution.

Si l'asynchronie ne se produit pas uniformément, alors l'ordre dans lequel ils sont enregistrés dans la file d'attente asynchrone sera dans le désordre.


for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val), 100 * Math.random());
}
// => 实际结果是随机的,依次为:4, 2, 3, 1

Les résultats renvoyés sont dans le désordre et incontrôlables, ce qui est l'asynchrone le plus réaliste. Mais une autre situation est que dans une boucle, que devez-vous faire si vous souhaitez que l'exécution asynchrone précédente soit terminée et que l'exécution asynchrone suivante soit à nouveau exécutée ?


for (let val of ['a', 'b', 'c', 'd']) {
  // a 执行完后,进入下一个循环
  // 执行 b,依此类推
}

N'est-ce pas juste plusieurs « séries » asynchrones !

La méthode consistant à imbriquer les opérations asynchrones dans le rappel puis à rappeler résout ce problème ! Alternativement, utiliser Promise + then() pour imbriquer les couches peut également résoudre le problème. Cependant, si vous insistez pour écrire cette méthode d'imbrication en boucle, je crains que cela ne pose encore beaucoup de problèmes. Je me demande, existe-t-il un meilleur moyen ?

Solution de synchronisation asynchrone

Imaginez si vous souhaitez envoyer un lot de données au serveur, seul le lot précédent est envoyé avec succès (c'est-à-dire que le serveur renvoie un réponse réussie), alors seulement commence l'envoi du prochain lot de données, sinon l'envoi est terminé. Il s'agit d'un exemple typique d'"opérations asynchrones interdépendantes dans une boucle for".

Évidemment, cet asynchrone "série" peut en fait être considéré comme une synchronisation. Cela prend plus de temps qu'un asynchrone dans le désordre. Logiquement parlant, nous voulons que le programme s'exécute de manière asynchrone, juste pour « ignorer » le blocage et passer moins de temps. Mais au contraire, si nous avons besoin d’une série de « séries » asynchrones, comment bien programmer ?

Pour cet asynchrone "série", ES6 peut facilement résoudre ce problème.


async function task () {
  for (let val of [1, 2, 3, 4]) {
    // await 是要等待响应的
    let result = await send(val);
    if (!result) {
      break;
    }
  }
}
task();

Littéralement, c'est ce cycle. Une fois les résultats obtenus, le cycle suivant sera réalisé. Par conséquent, la boucle est mise en pause (« bloquée ») à chaque exécution jusqu'à la fin de la boucle. Cette implémentation de codage élimine efficacement le problème de « l'enfer des rappels » imbriqués et réduit les difficultés cognitives.

C'est la solution pour synchroniser les problèmes asynchrones. Concernant cette solution, si Promise résout principalement le problème des rappels asynchrones, alors async + wait résout principalement le problème de la synchronisation des problèmes asynchrones et réduit la charge cognitive de la programmation asynchrone.

async + wait "différent à l'extérieur mais pareil à l'intérieur"

Quand je suis entré en contact avec cette API plus tôt, j'ai regardé la lourde documentation et Je pensais que async + wait était principalement utilisé pour résoudre le problème. Les problèmes asynchrones sont synchrones.

Ce n’est pas le cas. Comme le montre l'exemple ci-dessus : le mot-clé async déclare une fonction asynchrone. Il y a une ligne d'instructions wait dans le corps de cette fonction asynchrone, qui notifie que le comportement est exécuté de manière synchrone et que les codes adjacents ci-dessus et ci-dessous sont exécutés. ligne par ligne dans l’ordre.

Pour traduire à nouveau cette chose formelle, c'est :

1 Une fois la fonction asynchrone exécutée, un objet de promesse est toujours renvoyé
2. est synchrone

Parmi eux, 1 montre que de l'extérieur, la méthode tâche renvoie un objet Promise après exécution. Parce qu'elle renvoie une Promise, on peut comprendre que la tâche est une méthode asynchrone. Il ne fait aucun doute qu'il est utilisé comme ceci :


task().then((val) => {alert(val)})
   .then((val) => {alert(val)})

2 montre qu'à l'intérieur de la fonction tâche, l'asynchrone a été "coupé" en synchronisation. Le tout n’est qu’une fonction qui prend un peu de temps à s’exécuter.

En synthétisant 1 et 2, d'un point de vue formel, "la tâche dans son ensemble est une fonction asynchrone, et toute la partie interne est synchrone", ce qui est dit "différent à l'extérieur mais le pareil à l'intérieur".

La fonction entière est une fonction asynchrone, ce qui n'est pas difficile à comprendre. En termes d'implémentation, autant l'inverser. Au niveau du langage, lorsque le mot-clé async est appelé, une promesse est obligatoirement ajoutée à la fin de l'exécution de la fonction. Le retour :


async fn () {
  let result;
  // ...
  //末尾返回 promise
  return isPromise(result)? 
      result : Promise.resolve(undefined);
}
est en interne Comment se fait la synchronisation ? En fait, l'appel d'attente provoque l'exécution récursive des instructions (fonctions) suivantes. Elle ne sera résolue que lorsque le résultat sera obtenu et que son statut sera modifié. Ce n'est qu'une fois la résolution résolue que la ligne de code d'attente sera considérée comme terminée. et il continuera à exécuter jusqu'à la ligne suivante. Par conséquent, bien qu'il y ait une grande boucle for à l'extérieur, la boucle for entière est sérialisée en séquence.

Ainsi, rien qu'à partir de l'apparence du framework ci-dessus, il n'est pas difficile de comprendre la signification de async + wait. C'est tellement simple à utiliser, mais Promise est un morceau de base qui doit être maîtrisé.

秉承本次《重读 ES6》系列的原则,不过多追求理解细节和具体实现过程。我们继续巩固一下这个 “形式化” 的理解。

async + await 的进一步理解

有这样的一个异步操作 longTimeTask,已经用 Promise 进行了包装。借助该函数进行一系列验证。


const longTimeTask = function (time) {
 return new Promise((resolve, reject) => {
  setTimeout(()=>{
   console.log(`等了 ${time||'xx'} 年,终于回信了`);
   resolve({'msg': 'task done'});
  }, time||1000)
 })
}

async 函数的执行情况

如果,想查看 async exec1 函数的返回结果,以及 await 命令的执行结果:


const exec1 = async function () {
 let result = await longTimeTask();
 console.log('result after long time ===>', result);
}
// 查看函数内部执行顺序
exec1();
// => 等了 xx 年,终于回信了
// => result after long time ===> Object {msg: "task done"}

//查看函数总体返回值
console.log(exec1());
// => Promise {[[PromiseStatus]]: "pending",...}
// => 同上

以上 2 步执行,清晰的证明了 exec1 函数体内是同步、逐行逐行执行的,即先执行完异步操作,然后进行 console.log() 打印。而 exec1() 的执行结果就直接是一个 Promise,因为它最先会蹦出来一串 Promise ...,然后才是 exec1 函数的内部执行日志。

因此,所有验证,完全符合 整体是一个异步函数,内部整个是同步的 的总结。

await 如何执行其后语句?

回到 await ,看看它是如何执行其后边的语句的。假设:让 longTimeTask() 后边直接带 then() 回调,分两种情况:

1)then() 中不再返回任何东西
2) then() 中继续手动返回另一个 promise


const exec2 = async function () {
 let result = await longTimeTask().then((res) => {
  console.log('then ===>', res.msg);
  res.msg = `${res.msg} then refrash message`;
  // 注释掉这条 return 或 手动返回一个 promise
  return Promise.resolve(res);
 });
 console.log('result after await ===>', result.msg);
}
exec2();
// => 情况一 TypeError: Cannot read property 'msg' of undefined
// => 情况二 正常

首先,longTimeTask() 加上再多得 then() 回调,也不过是放在了它的回调列队 queue 里了。也就是说,await 命令之后始终是一条 表达式语句,只不过上述代码书写方式比较让人迷惑。(比较好的实践建议是,将 longTimeTask 方法身后的 then() 移入 longTimeTask 函数体封装起来)

其次,手动返回另一个 promise 和什么也不返回,关系到 longTimeTask() 方法最终 resolve 出去的内容不一样。换句话说,await 命令会提取其后边的promise 的 resolve 结果,进而直接导致 result 的不同。

值得强调的是,await 命令只认 resolve 结果,对 reject 结果报错。不妨用以下的 return 语句替换上述 return 进行验证。


return Promise.reject(res);

最后

其实,关于异步编程还有很多可以梳理的,比如跨模块的异步编程、异步的单元测试、异步的错误处理以及什么是好的实践。All in all, 限于篇幅,不在此汇总了。最后,async + await 确实是一个很优雅的方案。

相关推荐:

让Express支持async方法分享

NodeJs通过async和await处理异步的方法

Node.js中如何使用async函数

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
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