Cet article vous apporte une analyse approfondie des sondages d'événements asynchrones JavsScript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
JavsScript est un langage de programmation monothread, ce qui signifie qu'il ne peut traiter qu'une seule chose à la fois. C'est-à-dire que le moteur JavaScript ne peut traiter qu'une seule chose dans un seul thread. à la fois.
Bien que le monothreading simplifie le code de programmation car vous n'avez pas à vous soucier trop des problèmes causés par la concurrence, cela signifie également que vous effectuerez des opérations à long terme, telles que des requêtes réseau, tout en bloquant le thread principal.
Imaginez demander des données à une API. Selon les circonstances, le serveur mettra un certain temps à traiter la demande tout en bloquant le thread principal, laissant la page Web insensible pendant une longue période.
C'est pourquoi le JavaScript asynchrone a été introduit. En utilisant JavaScript asynchrone (comme les fonctions de rappel, les promesses, async/await), vous pouvez effectuer des requêtes réseau pendant une longue période sans bloquer le thread principal :)
Peut-être savez-vous comment fonctionne le JavaScript asynchrone, ce n'est pas le cas Mais sachant comment cela fonctionne, une compréhension plus approfondie de l'async JavaScript est utile.
Alors sans plus attendre, commençons :)
Avant de nous plonger dans le JavaScript
asynchrone, comprenons d'abord comment le code JavaScript
synchrone s'exécute dans le moteur JavaScript
. Par exemple :
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();
Pour comprendre comment le code ci-dessus est exécuté dans le moteur JavaScript, nous devons comprendre les concepts de contexte d'exécution et de pile d'appels (également appelée pile d'exécution).
Le code de fonction est exécuté dans le contexte d'exécution de la fonction et le code global est exécuté dans le contexte d'exécution global. Chaque fonction possède son propre contexte d'exécution.
La pile d'appels, comme son nom l'indique, est une pile avec une structure LIFO (dernier entré, premier sorti) qui est utilisée pour stocker tous les contextes d'exécution créés lors de l'exécution du code.
JavaScript n'a qu'une seule pile d'appels car il s'agit d'un langage de programmation monothread. La pile d'appels a une structure LIFO, ce qui signifie que les éléments ne peuvent être ajoutés ou supprimés que depuis le haut de la pile.
Revenons à l'extrait de code ci-dessus et essayons de comprendre comment le code est exécuté dans le moteur JavaScript.
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();
Lorsque ce code est exécuté, un contexte d'exécution global est créé (par main () signifie) et le pousse vers le haut de la pile d'appels. Lorsqu'un appel à first() est rencontré, il est placé en haut de la pile.
Ensuite, console.log('Salut !') est poussé vers le haut de la pile et une fois terminé, il est retiré de la pile. Après cela, nous appelons second(), donc la fonction second() est poussée en haut de la pile.
console.log('Bonjour !') est poussé vers le haut de la pile et retiré de la pile une fois terminé. La fonction second() se termine, elle est donc retirée de la pile.
console.log("the End") est placé en haut de la pile et supprimé une fois terminé. Après cela, la fonction first() se termine et est donc supprimée de la pile.
Le programme a terminé son exécution à ce stade, donc le contexte d'exécution global (main()) est retiré de la pile.
Maintenant que nous avons une compréhension de base de la pile d'appels et du fonctionnement du JavaScript synchrone, revenons au JavaScript asynchrone.
Supposons que nous effectuons un traitement d'image ou des requêtes réseau de manière synchrone. Par exemple :
const processImage = (image) => { /** * doing some operations on image **/ console.log('Image processed'); } const networkRequest = (url) => { /** * requesting network resource **/ return someData; } const greeting = () => { console.log('Hello World'); } processImage(logo.jpg); networkRequest('www.somerandomurl.com'); greeting();
Le traitement d'image et les requêtes réseau prennent du temps, et lorsque la fonction processImage() est appelée, cela prendra un certain temps en fonction de la taille de l'image.
Une fois la fonction processImage() terminée, elle sera supprimée de la pile. Ensuite, la fonction networkRequest() est appelée et poussée sur la pile. De même, l’exécution prend également un certain temps.
Enfin, lorsque la fonction networkRequest() se termine, la fonction Greeting() est appelée car elle ne contient qu'une console. Journaliser les instructions et la console. Les instructions de journalisation sont généralement rapides, donc la fonction Greeting() s'exécute et renvoie immédiatement.
Par conséquent, nous devons attendre que la fonction (comme processImage() ou networkRequest()) se termine. Cela signifie que ces fonctions bloquent la pile d'appels ou le thread principal. Par conséquent, pendant l’exécution du code ci-dessus, nous ne pouvons effectuer aucune autre opération, ce qui n’est pas idéal.
La solution la plus simple est un rappel asynchrone. Nous utilisons des rappels asynchrones pour rendre le code non bloquant. Par exemple :
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest();
Ici, j'ai utilisé la méthode setTimeout pour simuler une requête réseau. N'oubliez pas que setTimeout ne fait pas partie du moteur JavaScript, il fait partie de l'API Web (dans le navigateur) et de l'API C/c++ (dans node.js).
Afin de comprendre comment ce code est exécuté, nous devons comprendre davantage de concepts, tels que l'interrogation d'événements et les files d'attente de rappel (ou files d'attente de messages).
事件轮询、web api和消息队列不是JavaScript引擎的一部分,而是浏览器的JavaScript运行时环境或Nodejs JavaScript运行时环境的一部分(对于Nodejs)。在Nodejs中,web api被c/c++ api所替代。
现在让我们回到上面的代码,看看它是如何异步执行的。
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest(); console.log('The End');
当上述代码在浏览器中加载时,console.log(' Hello World ') 被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对 networkRequest() 的调用,因此将它推到堆栈的顶部。
下一个 setTimeout() 函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数:
1) 回调和
2) 以毫秒(ms)为单位的时间。
setTimeout() 方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。cosole.log(“the end”) 被推送到堆栈中,在完成后执行并从堆栈中删除。
同时,计时器已经过期,现在回调被推送到消息队列。但是回调不会立即执行,这就是事件轮询开始的地方。
事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。如果调用堆栈是空的,它将检查消息队列,看看是否有任何挂起的回调等待执行。
在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部。
然后是 console.log(“Async Code”) 被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。
消息队列还包含来自DOM事件(如单击事件和键盘事件)的回调。例如:
document.querySelector('.btn').addEventListener('click',(event) => { console.log('Button Clicked'); });
对于DOM事件,事件侦听器位于web api环境中,等待某个事件(在本例中单击event)发生,当该事件发生时,回调函数被放置在等待执行的消息队列中。
同样,事件轮询检查调用堆栈是否为空,并在调用堆栈为空并执行回调时将事件回调推送到堆栈。
我们还可以使用setTimeout
来延迟函数的执行,直到堆栈清空为止。例如
const bar = () => { console.log('bar'); } const baz = () => { console.log('baz'); } const foo = () => { console.log('foo'); setTimeout(bar, 0); baz(); } foo();
打印结果:
foo baz bar
当这段代码运行时,第一个函数foo()被调用,在foo内部我们调用console.log('foo'),然后setTimeout()被调用,bar()作为回调函数和时0秒计时器。
现在,如果我们没有使用 setTimeout, bar() 函数将立即执行,但是使用 setTimeout 和0秒计时器,将bar的执行延迟到堆栈为空的时候。
0秒后,bar()回调被放入等待执行的消息队列中。但是它只会在堆栈完全空的时候执行,也就是在baz和foo函数完成之后。
我们已经了解了异步回调和DOM事件是如何执行的,它们使用消息队列存储等待执行所有回调。
ES6引入了任务队列的概念,任务队列是 JavaScript 中的 promise 所使用的。消息队列和任务队列的区别在于,任务队列的优先级高于消息队列,这意味着任务队列中的promise 作业将在消息队列中的回调之前执行,例如:
const bar = () => { console.log('bar'); }; const baz = () => { console.log('baz'); }; const foo = () => { console.log('foo'); setTimeout(bar, 0); new Promise((resolve, reject) => { resolve('Promise resolved'); }).then(res => console.log(res)) .catch(err => console.log(err)); baz(); }; foo();
打印结果:
foo baz Promised resolved bar
我们可以看到 promise 在 setTimeout 之前执行,因为 promise 响应存储在任务队列中,任务队列的优先级高于消息队列。
因此,我们了解了异步 JavaScript 是如何工作的,以及调用堆栈、事件循环、消息队列和任务队列等概念,这些概念共同构成了 JavaScript 运行时环境。虽然成为一名出色的JavaScript开发人员并不需要学习所有这些概念,但是了解这些概念是有帮助的:)
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!