Maison > interface Web > js tutoriel > Explication détaillée des fonctions de file d'attente JavaScript et de l'exécution asynchrone

Explication détaillée des fonctions de file d'attente JavaScript et de l'exécution asynchrone

小云云
Libérer: 2017-12-18 10:57:01
original
2033 Les gens l'ont consulté

J'ai vu une fonction de file d'attente similaire en faisant référence au code JavaScript d'autres personnes, mais je ne l'ai pas comprise. Il s'avère que c'est pour garantir que les fonctions sont appelées dans l'ordre. Cet article vous présente principalement les fonctions de file d'attente JavaScript et les méthodes d'exécution asynchrone. J'espère qu'il pourra vous aider.

Supposons que vous ayez plusieurs fonctions fn1, fn2 et fn3 qui doivent être appelées dans l'ordre. La manière la plus simple est bien sûr :

fn1();fn2();fn3();
Copier après la connexion

Mais parfois, ces fonctions sont ajoutées une par une. runtime. Oui, vous ne savez pas quelles fonctions il y a lors de l'appel ; à ce stade, vous pouvez prédéfinir un tableau, y insérer les fonctions lors de l'ajout de fonctions et les retirer du tableau une par une dans l'ordre. nécessaire, et appelez-les dans l'ordre :

var stack = [];// 执行其他操作,定义fn1stack.push(fn1);// 执行其他操作,定义fn2、fn3stack.push(fn2, fn3);// 调用的时候stack.forEach(function(fn) { fn() });
Copier après la connexion

Peu importe que la fonction ait un nom ou non, vous pouvez simplement transmettre la fonction anonyme directement. Testons-le :

var stack = [];function fn1() {
    console.log('第一个调用');
}
stack.push(fn1);function fn2() {
    console.log('第二个调用');
}
stack.push(fn2, function() { console.log('第三个调用') });

stack.forEach(function(fn) { fn() }); // 按顺序输出'第一个调用'、'第二个调用'、'第三个调用'
Copier après la connexion

Cette implémentation fonctionne bien jusqu'à présent, mais nous avons ignoré une situation, qui est l'appel de fonctions asynchrones. L'asynchronie est un sujet incontournable en JavaScript. Je ne vais pas aborder ici les différents termes et concepts liés à l'asynchrone en JavaScript. Les lecteurs sont invités à le vérifier par eux-mêmes (comme un commentaire célèbre). Si vous savez que le code suivant affichera 1, 3 et 2, continuez à lire :

console.log(1);

setTimeout(function() {
    console.log(2);
}, 0);console.log(3);
Copier après la connexion

S'il existe une fonction dans la file d'attente de la pile qui est une fonction asynchrone similaire, notre implémentation sera foiré :

var stack = [];function fn1() { console.log('第一个调用') };
stack.push(fn1);function fn2() {
    setTimeout(function fn2Timeout() {
         console.log('第二个调用');
    }, 0);
}
stack.push(fn2, function() { console.log('第三个调用') });

stack.forEach(function(fn) { fn() }); // 输出'第一个调用'、'第三个调用'、'第二个调用'
Copier après la connexion

Le problème est évident. fn2 est bien appelé dans l'ordre, mais la fonction fn2Timeout() { console.log('Second call') } dans setTimeout n'est pas exécutée immédiatement (même si timeout est défini sur 0); fn2 revient immédiatement après avoir été appelé, puis exécute fn3. Une fois fn3 exécuté, c'est en fait le tour de fn2Timeout.
Comment le résoudre ? Après notre analyse, la clé ici est fn2Timeout. Nous devons attendre qu'elle soit réellement exécutée avant d'appeler fn3. Idéalement, cela ressemblerait à ceci :

function fn2() {
    setTimeout(function() {
        fn2Timeout();
        fn3();
    }, 0);
}
Copier après la connexion

Mais cela équivaut à remplacer entièrement le fn2Timeout d'origine. . dans une nouvelle fonction, puis insérez les fn2Timeout et fn3 d'origine. Cette méthode de modification dynamique de la fonction d'origine porte un terme spécial appelé Monkey Patch. Selon le mantra de nos programmeurs : "Cela peut certainement être fait", mais c'est un peu délicat à écrire, et il est facile de s'impliquer. Existe-t-il une meilleure façon ?
Nous prenons du recul et n'insistons pas pour attendre que fn2Timeout soit complètement exécuté avant d'exécuter fn3. Au lieu de cela, nous l'appelons sur la dernière ligne du corps de la fonction fn2Timeout :

function fn2() {
    setTimeout(function fn2Timeout() {
        console.log('第二个调用');
        fn3();       // 注{1}
    }, 0);
}
Copier après la connexion

Cela ressemble. mieux, mais définissez fn2 Il n'y avait pas de fn3 à l'époque. D'où vient ce fn3 ?

Il y a un autre problème. Puisque fn3 doit être appelé dans fn2, nous ne pouvons pas appeler fn3 via stack.forEach, sinon fn3 sera appelé deux fois.

Nous ne pouvons pas écrire fn3 dans fn2. Au lieu de cela, il nous suffit de trouver la fonction suivante de fn2 dans la pile à la fin de fn2Timeout, puis d'appeler :

function fn2() {
    setTimeout(function fn2Timeout() {
        console.log('第二个调用');        next();
    }, 0);
}
Copier après la connexion

Cette fonction suivante est chargée de trouver la fonction suivante dans la pile et de l'exécuter. . Implémentons next maintenant :

var index = 0;

function next() {
    var fn = stack[index];    index = index + 1; // 其实也可以用shift 把fn 拿出来    if (typeof fn === 'function') fn();
}next通过stack[index]去获取stack中的函数,每调用next一次index会加1,从而达到取出下一个函数的目的。
Copier après la connexion

next est utilisé comme ceci :

var stack = [];

// 定义index 和nextfunction fn1() {
    console.log('第一个调用');    next();  // stack 中每一个函数都必须调用`next`
};
stack.push(fn1);function fn2() {
    setTimeout(function fn2Timeout() {
         console.log('第二个调用');         next();  // 调用`next`
    }, 0);
}
stack.push(fn2, function() {
    console.log('第三个调用');    next(); // 最后一个可以不调用,调用也没用。
});next(); // 调用next,最终按顺序输出'第一个调用'、'第二个调用'、'第三个调用'。
现在stack.forEach一行已经删掉了,我们自行调用一次next,next会找出stack中的第一个函数fn1执行,fn1 里调用next,去找出下一个函数fn2并执行,fn2里再调用next,依此类推。
Copier après la connexion

Next doit être appelé dans chaque fonction S'il n'est pas écrit dans une fonction, après l'exécution de la fonction. Le programme se terminera directement sans aucun mécanisme pour continuer.

Après avoir compris l'implémentation de la file d'attente des fonctions, vous devriez être capable de résoudre la question d'entretien suivante :

// 实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)/* 输出: 
Hi! This is Hank!
*/LazyMan(“Hank”).sleep(10).eat(“dinner”)输出/* 输出: 
Hi! This is Hank!
// 等待10秒..
Wake up after 10
Eat dinner~
*/LazyMan(“Hank”).eat(“dinner”).eat(“supper”)/* 输出: 
Hi This is Hank!
Eat dinner~
Eat supper~
*/LazyMan(“Hank”).sleepFirst(5).eat(“supper”)/* 等待5秒,输出
Wake up after 5
Hi This is Hank!
Eat supper
*/// 以此类推。
Node.js 中大名鼎鼎的connect框架正是这样实现中间件队列的。有兴趣可以去看看它的源码或者这篇解读《何为 connect 中间件》。
Copier après la connexion

Si vous faites attention, vous verrez peut-être que cette suivante ne peut être placée que dans la fonction pour le moment, si elle est placée au milieu, le problème d'origine apparaîtra toujours :

function fn() {
    console.log(1);
    next();
    console.log(2); // next()如果调用了异步函数,console.log(2)就会先执行}
Copier après la connexion

Redux et koa, grâce à différentes implémentations, peuvent mettre next au milieu de. la fonction, exécutez les fonctions suivantes puis revenez en arrière pour exécuter ensuite Le code ci-dessous est très intelligent. Écrivez à nouveau quand vous aurez le temps.

Recommandations associées :

Partagez un code de fonction de file d'attente d'événements javascript encapsulé pour résoudre le problème de liaison des événements

Votre propre événement javascript encapsulé Fonction de file d'attente version_javascript skills

Utilisation de la fonction de file d'attente jquery example_jquery

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!

É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