Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung des Implementierungscodes für die asynchrone Ausführung der JavaScript-Funktion

Detaillierte Erläuterung des Implementierungscodes für die asynchrone Ausführung der JavaScript-Funktion

伊谢尔伦
伊谢尔伦Original
2017-07-22 10:56:201581Durchsuche

Angenommen, Sie haben mehrere Funktionen fn1, fn2 und fn3, die nacheinander aufgerufen werden müssen:


fn1();
fn2();
fn3();

Aber manchmal werden diese Funktionen zur Laufzeit einzeln hinzugefügt und Sie wissen nicht, welche Funktionen vorhanden sind, wenn Sie sie zu diesem Zeitpunkt aufrufen. Sie können ein Array vordefinieren und die Funktionen beim Hinzufügen der Funktionen hineinschieben , und bestellen Sie sie bei Bedarf einzeln aus dem Array und rufen Sie sie einzeln auf:


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

Es spielt keine Rolle, ob ob die Funktion einen Namen hat oder nicht. Sie können die anonyme Funktion auch direkt übergeben. Testen wir es:


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

function fn2() {
  console.log('第二个调用');
}
stack.push(fn2, function() { console.log('第三个调用') });

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

Diese Implementierung funktioniert bisher gut, aber wir haben eine Situation ignoriert, nämlich den Aufruf asynchroner Funktionen. Asynchronität ist ein unvermeidliches Thema in JavaScript. Ich werde hier nicht auf die verschiedenen Begriffe und Konzepte im Zusammenhang mit Asynchronität in JavaScript eingehen. Die Leser werden gebeten, sich selbst damit auseinanderzusetzen (z. B. einen berühmten Kommentar). Wenn Sie wissen, dass der folgende Code 1, 3 und 2 ausgibt, lesen Sie bitte weiter:


console.log(1);

setTimeout(function() {
  console.log(2);
}, 0);

console.log(3);

Wenn sich eine Funktion im Stapel befindet queue: Unsere Implementierung ähnlicher asynchroner Funktionen ist durcheinander:


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() }); // 输出'第一个调用'、'第三个调用'、'第二个调用'

Das Problem ist offensichtlich, fn2 wird zwar nacheinander aufgerufen, aber die Funktion fn2Timeout in setTimeout () { console.log('Second call') } wird nicht sofort ausgeführt (auch wenn timeout auf 0 gesetzt ist); nach der Ausführung von fn3 kehrt fn2Timeout tatsächlich an die Reihe .

Wie löst man das Problem? Lassen Sie es uns analysieren. Der Schlüssel hier ist fn2Timeout. Wir müssen warten, bis er tatsächlich ausgeführt wird, bevor wir fn3 aufrufen Dies entspricht jedoch dem Entfernen des ursprünglichen fn2Timeout und dem Ersetzen durch eine neue Funktion und dem anschließenden Einfügen des ursprünglichen fn2Timeout und fn3. Für diese Methode zur dynamischen Änderung der ursprünglichen Funktion gibt es einen speziellen Begriff namens Monkey Patch. Ganz nach dem Mantra unserer Programmierer: „Es ist auf jeden Fall machbar“, aber das Schreiben ist etwas umständlich und man kann sich leicht darauf einlassen. Gibt es einen besseren Weg?
Wir gehen einen Schritt zurück und bestehen nicht darauf, auf die vollständige Ausführung von fn2Timeout zu warten, bevor wir fn3 ausführen. Stattdessen rufen wir es in der letzten Zeile des fn2Timeout-Funktionskörpers auf:


function fn2() {
  setTimeout(function() {
    fn2Timeout();
    fn3();
  }, 0);
}


Das sieht besser aus, aber als fn2 definiert wurde, gab es noch kein fn3. Woher kam fn3?

Es gibt ein weiteres Problem. Da fn3 in fn2 aufgerufen werden muss, können wir fn3 nicht über stack.forEach aufrufen, da sonst fn3 zweimal aufgerufen wird.


Wir können fn3 nicht in fn2 schreiben. Stattdessen müssen wir nur die nächste Funktion von fn2 im Stapel am Ende von fn2Timeout finden und dann Folgendes aufrufen:

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

Diese nächste Funktion ist Verantwortlich dafür, den Stapel zu finden und die nächste Funktion darin auszuführen. Lassen Sie uns jetzt next implementieren:


function fn2() {
  setTimeout(function fn2Timeout() {
    console.log('第二个调用');
    next();
  }, 0);
}

next verwendet stack[index], um die Funktion im Stapel abzurufen. Der Index wird jedes Mal um 1 erhöht aufgerufen, also Um den Zweck zu erreichen, die nächste Funktion herauszunehmen.
next wird wie folgt verwendet:


var index = 0;

function next() {
  var fn = stack[index];
  index = index + 1; // 其实也可以用shift 把fn 拿出来
  if (typeof fn === 'function') fn();
}


Nachdem die stack.forEach-Zeile gelöscht wurde, rufen wir next selbst auf und next will Finden Sie heraus, was sich im Stapel befindet. Die erste Funktion fn1 wird ausgeführt, next wird in fn1 aufgerufen, um die nächste Funktion fn2 zu finden und auszuführen, dann wird next in fn2 aufgerufen und so weiter.
Jede Funktion muss als nächstes aufgerufen werden. Wenn sie nicht in einer bestimmten Funktion geschrieben ist, wird das Programm direkt nach der Ausführung der Funktion beendet, ohne dass es einen Mechanismus zum Fortfahren gibt.

Nachdem Sie diese Implementierung der Funktionswarteschlange verstanden haben, sollten Sie in der Lage sein, die folgende Interviewfrage zu lösen:

var stack = [];

// 定义index 和next

function 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,最终按顺序输出'第一个调用'、'第二个调用'、'第三个调用'。


Node.js This So implementiert das berühmte Connect-Framework von Zhongda Middleware-Warteschlangen.

Wenn Sie vorsichtig sind, können Sie feststellen, dass dieser nächste Schritt vorerst nur am Ende der Funktion platziert werden kann. Wenn er in der Mitte platziert wird, tritt das ursprüngliche Problem weiterhin auf:

// 实现一个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
*/

// 以此类推。


Durch unterschiedliche Implementierungen können Redux und Koa in der Mitte der Funktion als nächstes platziert werden und anschließend wieder zurückgedreht werden Als nächstes den folgenden Code ausführen, was sehr clever ist. Schreiben Sie noch einmal, wenn Sie Zeit haben.

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Implementierungscodes für die asynchrone Ausführung der JavaScript-Funktion. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn