Heim  >  Artikel  >  Web-Frontend  >  Verstehen Sie, wie Asynchronität in JavaScript gehandhabt wird

Verstehen Sie, wie Asynchronität in JavaScript gehandhabt wird

青灯夜游
青灯夜游nach vorne
2020-11-20 17:29:059335Durchsuche

Verstehen Sie, wie Asynchronität in JavaScript gehandhabt wird

Bei der Website-Entwicklung sind asynchrone Ereignisse ein unvermeidlicher Bestandteil des Projekts. Auch aufgrund des Aufkommens von Front-End-Frameworks ist die durch Frameworks implementierte SPA zu einem Standard für die schnelle Erstellung von Daten geworden. In diesem Artikel geht es um die asynchrone Verarbeitung in JavaScript.

Synchronisieren? asynchron?

Zuallererst müssen wir natürlich verstehen, was Synchronisation bzw. Asynchronität bedeutet.

Diese beiden Begriffe sind für Anfänger immer verwirrend. Schließlich ist die wörtliche Bedeutung auf Chinesisch im Umkehrschluss leicht zu verstehen Dinge parallel zusammen.

Wenn wir beispielsweise zur Bank gehen, um Geschäfte abzuwickeln, ist das Anstehen vor dem Fenster eine synchrone Ausführung, und das Abrufen der Nummer und das Erledigen anderer Dinge ist eine asynchrone Ausführung durch die Eigenschaften von Event Loop, asynchronen Ereignissen in JavaScript Man kann sagen, dass es ein Kinderspiel ist

Wie kann man also mit asynchronen Ereignissen in JavaScript umgehen?

Rückruffunktion

Am bekanntesten ist die Rückruffunktion. Beispielsweise müssen Ereignis-Listener, die registriert werden, wenn eine Webseite mit Benutzern interagiert, eine Rückruffunktion erhalten, oder andere Web-API-Funktionen wie setTimeout und xhr müssen ebenfalls einen Rückruf erhalten Die Funktion kann durch die Übergabe einer Rückruffunktion zum vom Benutzer gewünschten Zeitpunkt ausgelöst werden. Schauen wir uns zunächst ein Beispiel für setTimeout an: setTimeoutxhr,也都能通过传递回调函数在用户要求的时机去触发。先看一个 setTimeout 的例子:

// callback
function withCallback() {
  console.log('start')
  setTimeout(() => {
    console.log('callback func')
  }, 1000)
  console.log('done')
}withCallback()
// start
// done
// callback func

setTimeout 被执行后,当过了指定的时间间隔之后,回调函数会被放到队列的末端,再等待事件循环处理到它。

注意:也就时因为这种机制,开发者设定给 setTimeout 的时间间隔,并不会精准的等于从执行到触发所经过的时间,使用时要特别注意!

回调函数虽然在开发中十分常见,但也有许多难以避免的问题。例如由于函数需要被传递给其他函数,开发者难以掌控其他函数内的处理逻辑;又因为回调函数仅能配合 try … catch 捕捉错误,当异步错误发生时难以控制;另外还有最著名的“回调地狱”。

Promise

幸好在 ES6 之后出现了 Promise,拯救了身陷在地狱的开发者们。其基本用法也很简单:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2

之前讨论 Event Loop 时没有提到的是,在HTML 5 的Web API 标准 中,Event Loop 新增了微任务队列(micro task queue),而 Promise 正是通过微任务队列来驱动它的;微任务队列的触发时机是在栈被清空时,JavaScript 引擎会先确认微任务队列有没有东西,有的话就优先执行,直到清空后才从队列拿出新任务到栈上。

如上面的例子,当函数回传一个 Promise 时,JavaScript 引擎便会把后传入的函数放到微任务队列中,反复循环,输出了上列的结果。后续的  .then 语法会回传一个新的 Promise,参数函数则接收前一个 Promise.resolve 的结果,凭借这样函数参数传递,让开发者可以管道式的按顺序处理异步事件。

如果在例子中加上 setTimeout 就更能清楚理解微任务与一般任务的差别:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => setTimeout(() => console.log('setTimeout'), 0))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2 -> 微任务优先执行
// setTimeout

另外,前面所说的回调函数很难处理的异步错误,也可以通过 .catch 语法来捕获。

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => { throw new Error('error') })
  .then(() => console.log('then 2'))
  .catch((err) => console.log('catch:', err))
// promise func
// then 1
// catch: error
//   ...error call stack

async await

从 ES6 Promise 问世之后,异步代码从回呼地狱逐渐变成了优雅的函数式管道处理,但对于不熟悉度的开发者来说,只不过是从回调地狱变成了 Promise 地狱而已。

在 ES8 中规范了新的 async/await,虽然只是 Promise 和 Generator Function组合在一起的语法糖,但通过 async/await 便可以将异步事件用同步语法来处理,就好像是老树开新花一样,写起来的风格与 Promise 完全不同:

function wait(time, fn) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('wait:', time)
      resolve(fn ? fn() : time)
    }, time)
  })
}
await wait(500, () => console.log('bar'))
console.log('foo')
// wait: 500
// bar
// foo

通过把 setTimeout 包装成 Promise,再用 await 关键字调用,可以看到结果会是同步执行的先出现 bar,再出现 foo,也就是开头提到的将异步事件写成同步处理。

再看一个例子:

async function withAsyncAwait() {
  for(let i = 0; i < 5; i++) {
    await wait(i*500, () => console.log(i))
  }
}await withAsyncAwait()
// wait: 0
// 0
// wait: 500
// 1
// wait: 1000
// 2
// wait: 1500
// 3
// wait: 2000
// 4

代码中实现了withAsyncAwait 函数,用 for 循环及 await 关键字反复执行 wait 函数;此处执行时,循环每次会按顺序等待不同的秒数再执行下一次循环。

在使用 async/await 时,由于 awaitrrreee

Nachdem setTimeout ausgeführt wurde und das angegebene Zeitintervall verstrichen ist, wird die Rückruffunktion am Ende des platziert in die Warteschlange und warten Sie dann, bis die Ereignisschleife damit umgeht. 🎜
Hinweis: Aufgrund dieses Mechanismus entspricht das von den Entwicklern auf setTimeout festgelegte Zeitintervall nicht genau der Zeit, die von der Ausführung bis zur Auslösung verstrichen ist !
🎜Obwohl Rückruffunktionen in der Entwicklung sehr verbreitet sind, weisen sie auch viele Probleme auf, die schwer zu vermeiden sind. Da Funktionen beispielsweise an andere Funktionen übergeben werden müssen, ist es für Entwickler schwierig, die Verarbeitungslogik in anderen Funktionen zu steuern, und da die Rückruffunktion nur mit try...catch zusammenarbeiten kann, um zu fangen Bei Fehlern ist es schwierig zu kontrollieren, wann ein asynchroner Fehler auftritt. Es gibt auch die berühmteste „Rückrufhölle“. 🎜🎜🎜Promise🎜🎜🎜Glücklicherweise erschien Promise nach ES6 und rettete Entwickler, die in der Hölle gefangen waren. Die grundlegende Verwendung ist ebenfalls sehr einfach: 🎜rrreee🎜Was bei der Erörterung von Event Loop zuvor nicht erwähnt wurde, ist, dass Event Loop im HTML 5-Web-API-Standard eine Mikro-Task-Warteschlange hinzugefügt hat und Promise eine Mikro-Task-Warteschlange verwendet Die Aufgabenwarteschlange wird ausgelöst, wenn der Stapel gelöscht wird. Die JavaScript-Engine überprüft zunächst, ob sich etwas in der Mikroaufgabenwarteschlange befindet neue Aufgaben aus der Warteschlange, bis sie auf dem Stapel gelöscht werden. 🎜🎜Wie im obigen Beispiel: Wenn die Funktion ein Promise zurückgibt, stellt die JavaScript-Engine die später übergebene Funktion in die Mikrotask-Warteschlange, wiederholt eine Schleife und gibt die oben aufgeführten Ergebnisse aus. Die nachfolgende .then-Syntax gibt ein neues Promise zurück und die Parameterfunktion erhält das Ergebnis des vorherigen Promise.resolve. Mit dieser Funktionsparameterübergabe können Entwickler Handle verarbeiten asynchrone Ereignisse nacheinander. 🎜🎜Wenn Sie dem Beispiel setTimeout hinzufügen, können Sie den Unterschied zwischen Mikrotasks und allgemeinen Aufgaben klarer verstehen: 🎜rrreee🎜Darüber hinaus sind die asynchronen Fehler, die mit der zuvor erwähnten Callback-Funktion nur schwer zu behandeln sind Sie können auch die Syntax .catch zum Erfassen übergeben. 🎜rrreee🎜🎜asynchrones Warten🎜🎜🎜Seit dem Aufkommen von ES6 Promise hat sich asynchroner Code allmählich von der Callback-Hölle zur eleganten funktionalen Pipeline-Verarbeitung gewandelt, aber für Entwickler, die damit nicht vertraut sind, ist es nur eine Abwechslung von der Callback-Hölle Versprich einfach die Hölle. 🎜🎜Der neue async/await ist in ES8 standardisiert. Obwohl es sich nur um Syntaxzucker für die Kombination von Promise und Generator Function handelt, wird er über asyncweitergeleitet > /await kann synchrone Syntax verwenden, um asynchrone Ereignisse zu verarbeiten, genau wie ein alter Baum, der neue Blumen blüht. Der Schreibstil ist völlig anders als Promise: 🎜rrreee🎜Durch die Verwendung von setTimeout code> wird in ein Promise gepackt und dann mit dem Schlüsselwort <code>await aufgerufen. Sie können sehen, dass als Ergebnis zuerst bar und dann foo angezeigt wird wird angezeigt, d. h. das Schreiben asynchroner Ereignisse in die eingangs erwähnte synchrone Verarbeitung. 🎜🎜Sehen Sie sich ein weiteres Beispiel an: 🎜rrreee🎜Die Funktion withAsyncAwait ist im Code implementiert und es werden die Schleife for und das Schlüsselwort await verwendet um die Funktion wait wiederholt auszuführen; wenn sie hier ausgeführt wird, wartet die Schleife nacheinander eine andere Anzahl von Sekunden, bevor sie die nächste Schleife ausführt. 🎜🎜Wenn Sie async/await verwenden, denken Sie daran, es am besten zu verwenden, da das Schlüsselwort await nur in der asynchronen Funktion ausgeführt werden kann zur gleichen Zeit. 🎜

Darüber hinaus müssen Sie bei der Verwendung von Schleifen zur Verarbeitung asynchroner Ereignisse beachten, dass viele nach ES6 bereitgestellte Array-Methoden nicht unterstützt werdenasync/await 语法,如果这里用 forEach 取代 for und das Ergebnis eine synchrone Ausführung ist, bei der alle 0,5 Sekunden Zahlen ausgegeben werden:

Zusammenfassung

Dieser Artikel ist einfach. Dieser Artikel stellt drei Möglichkeiten für JavaScript vor, die asynchrone Verarbeitung zu handhaben, und veranschaulicht die Reihenfolge der Codeausführung anhand einiger einfacher Beispiele, die die zuvor erwähnte Ereignisschleife widerspiegeln. Das Konzept der Mikrotask-Warteschlange wird hinzugefügt. Ich hoffe, es hilft Ihnen, synchrone und asynchrone Anwendungen zu verstehen.

Weitere Kenntnisse zum Thema Programmierung finden Sie unter: Einführung in die Programmierung! !

Das obige ist der detaillierte Inhalt vonVerstehen Sie, wie Asynchronität in JavaScript gehandhabt wird. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen