5 Lösungen für die asynchrone JS-Programmierung

Wir wissen, dass die Ausführungsumgebung der JS-Sprache „Single Thread“ ist. Der sogenannte „Single Thread“ bedeutet, dass immer nur eine Aufgabe gleichzeitig erledigt werden kann. Der Vorteil dieses Modus ist, dass er relativ einfach zu implementieren und zu implementieren ist Die Ausführungsumgebung ist relativ einfach, der Nachteil ist, dass es nur eine Aufgabe gibt, die lange dauert und die nachfolgenden Aufgaben in eine Warteschlange gestellt werden müssen, was die Ausführung des gesamten Programms verzögert. Um dieses Problem zu lösen, unterteilt die JS-Sprache den Ausführungsmodus von Tasks in zwei Typen: synchron (Synchronous)und asynchron (Asynchronous).

Lassen Sie uns darüber sprechen, warum Asynchronität wichtig ist. Wie kann man async verwenden, um potenziell blockierende Vorgänge effizient zu handhaben?

Warum brauchen Sie asynchron?

Im Allgemeinen werden Programme sequentiell ausgeführt, und es passiert immer nur eine Sache. Wenn eine Funktion vom Ergebnis einer anderen Funktion abhängt, kann sie nur auf das Ende dieser Funktion warten, um die Ausführung fortzusetzen, und aus Sicht des Benutzers ist das gesamte Programm beendet.

Wie Sie wahrscheinlich wissen, ist die Ausführungsumgebung der Javascript-Sprache ein "einzelner Thread".

Der sogenannte „Single Thread“ bedeutet, dass immer nur eine Aufgabe gleichzeitig erledigt werden kann. Wenn mehrere Aufgaben vorhanden sind, müssen sie in die Warteschlange gestellt werden, die vorherige Aufgabe wird abgeschlossen, die nächste Aufgabe wird ausgeführt und so weiter.

Der Vorteil dieses Modus ist, dass er relativ einfach zu implementieren ist und die Ausführungsumgebung relativ einfach ist; der Nachteil ist, dass, solange eine Aufgabe lange dauert, die nachfolgenden Aufgaben in eine Warteschlange gestellt werden müssen, was die Ausführung verzögert gesamtes Programm. Herkömmliche Browser reagieren nicht (vorgetäuschter Tod), oft weil ein bestimmter Teil des Javascript-Codes lange läuft (z. B. eine Endlosschleife), wodurch die gesamte Seite an dieser Stelle hängen bleibt und andere Aufgaben nicht ausgeführt werden können.

zum Beispiel

Mac-Benutzer können diesen sich drehenden Regenbogen-Cursor (oft als Strandball bezeichnet) erleben, durch den das Betriebssystem dem Benutzer mitteilt: „Das laufende Programm wartet darauf, dass etwas anderes beendet wird, bevor es weiter ausgeführt werden kann, und zwar so lange. Es ist an der Zeit, Sie müssen sich Sorgen machen, was los ist."

Bildbeschreibung hier einfügen

Es ist eine frustrierende Erfahrung, die Rechenleistung eines Computers nicht voll auszunutzen – besonders in einer Zeit, in der Computer im Allgemeinen Mehrkern-CPUs haben, ist es sinnlos, dort zu sitzen und zu warten, Sie können einfach andere Arbeiten auf einem anderen Prozessorkern erledigen, und benachrichtigt Sie, wenn Ihr Computer zeitaufwändige Aufgaben erledigt. Auf diese Weise können Sie gleichzeitig andere Aufgaben erledigen, und hier kommt die asynchrone Programmierung ins Spiel. Die von Ihnen verwendete Programmierumgebung (im Fall der Webentwicklung ist die Programmierumgebung der Webbrowser) ist dafür verantwortlich, Ihnen eine API zur asynchronen Ausführung solcher Aufgaben bereitzustellen.

1. Blockieren

Asynchrone Techniken sind sehr nützlich, insbesondere in der Webprogrammierung. Wenn eine Webanwendung im Browser intensive Operationen ausführt und die Kontrolle nicht an den Browser zurückgegeben hat, scheint der gesamte Browser einzufrieren, was als Blockierung ; zu diesem Zeitpunkt kann der Browser die Eingaben des Benutzers nicht weiter verarbeiten und andere Aufgaben bis zum ausführen Die Webanwendung gibt die Kontrolle über den Prozessor zurück.

Schauen wir uns einige Blockierungsbeispiele .

Beispiel: simple-sync.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Simple synchronous JavaScript example</title>
</head>
<body>
    <button>Click me</button>
    <script>
        const btn = document.querySelector('button');
        btn.addEventListener('click', () => {
            let start = new Date();
            let end;
            for (let i = 0; i < 10000000; i++) {
                let date = new Date();
                end = date;
            }
            let time = end - start;
            console.log('计算1千万个日期总耗时:' + time + 'ms');
            let pElem = document.createElement('p');
            pElem.textContent = '计算1千万个日期总耗时:' + time + 'ms';
            document.body.appendChild(pElem);
        });
    </script>
</body>
</html>
复制代码

Fügen Sie der Schaltfläche einen Ereignis-Listener hinzu, wenn auf die Schaltfläche geklickt wird, beginnt sie mit der Ausführung einer sehr zeitaufwändigen Aufgabe (Berechnung von 10 Millionen Datumsangaben und Anzeige der letzten zeitaufwändigen Zeit in der Konsole) und fügen Sie dann einen Absatz hinzu.

Um dieses Beispiel auszuführen, öffnen Sie die JavaScript-Konsole und klicken Sie auf die Schaltfläche – Sie werden feststellen, dass der Absatz nicht auf der Seite erscheint, bis das Datum berechnet ist und die letzte verstrichene Zeit in der Konsole angezeigt wird.

Die Wirkung ist wie folgt:

Bildbeschreibung hier einfügen

Der Code wird in der Reihenfolge des Quellcodes ausgeführt, und nur der vorherige Code wird beendet, der letztere Code wird ausgeführt.

Hinweis : Dieses Beispiel ist unrealistisch: Es kommt normalerweise in der Praxis nicht vor, niemand wird Daten 10 Millionen Mal berechnen, es bietet nur eine sehr intuitive Erfahrung.

2. Synchronisation

Um zu verstehen, was asynchrones JavaScript ist, sollten wir damit beginnen, genau zu verstehen, was synchrones JavaScript ist.

Vieles von dem, was wir gelernt haben, ist im Grunde synchron: Der Code wird ausgeführt, und der Browser gibt das Ergebnis so schnell wie möglich zurück. Schauen wir uns ein einfaches Beispiel an

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Simple synchronous JavaScript example</title>
  </head>
  <body>
    <button>Click me</button>
    <script>
    const btn = document.querySelector('button');
    btn.addEventListener('click', () => {
      alert('You clicked me!');

      let pElem = document.createElement('p');
      pElem.textContent = 'This is a newly-added paragraph.';
      document.body.appendChild(pElem);
    });
    </script>
  </body>
</html>
复制代码

Die Wirkung ist wie folgt:

Bildbeschreibung hier einfügen

这段代码, 一行一行的顺序执行:

  1. 先取得一个在DOM里面的 <button> 引用。

  2. 点击按钮的时候,添加一个 click 事件监听器:

    1. alert() 消息出现。
    2. 一旦alert 结束,创建一个<p> 元素。
    3. 给它的文本内容赋值。
    4. 最后,把这个段落放进网页。

每一个操作在执行的时候,其他任何事情都没有发生 — 网页的渲染暂停. 因为前篇文章提到过 JavaScript 是单线程. 任何时候只能做一件事情, 只有一个主线程,其他的事情都阻塞了,直到前面的操作完成。

所以上面的例子,点击了按钮以后,段落不会创建,直到在alert消息框中点击ok,段落才会出现,你可以自己试试

Note: 请记住,这个很重要,alert()在演示阻塞效果的时候非常有用,但是在正式代码里面,它就是一个噩梦。

3. 解决

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)

  • "同步模式"就是前面讲到的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

  • "异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。 Bildbeschreibung hier einfügen

异步编程的几种方法

1. 回调函数

回调函数是异步编程最基本的方法。

回调函数的概念:

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.

译过来就是:

回调函数是作为参数传递给另一个函数并在其父函数完成后执行的函数。

下面是一个回调函数的例子:

function doSomething(msg, callback){
    alert(msg);
    if(typeof callback == "function") 
    callback();
 } 
doSomething("回调函数", function(){
    alert("匿名函数实现回调!");
 }); 
复制代码

我们再来看几个经典的回调函数代码,我保证你一定用过他们:

◾ 1. 异步请求的毁掉函授:

$.get("/try/ajax/demo_test.php",function(data,status){
    alert("数据: " + data + "\n状态: " + status);
});
复制代码

◾ 2. 数组遍历的回调函数

array1.forEach(element => console.log(element));
复制代码

等等

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

回调函数 最致命的缺点,就是容易写出 回调地狱(Callback hell)。假设多个请求存在依赖性,你可能就会写出如下代码:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})
复制代码

2. 事件监听

另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

事件监听的回调函数:

element.addEventListener("click", function(){ 
	alert("Hello World!"); 
});
复制代码

上面这行代码的意思是,当 element 发生click事件,就执行传入的 function。

这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

3. 发布/订阅

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

  • 订阅者(Subscriber)把自己想订阅的事件 注册(Subscribe)到调度中心(Event Channel);
  • 发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由 调度中心 统一调度(Fire Event)订阅者注册到调度中心的处理代码。

◾ 例子

比如我们很喜欢看某个公众号号的文章,但是我们不知道什么时候发布新文章,要不定时的去翻阅;这时候,我们可以关注该公众号,当有文章推送时,会有消息及时通知我们文章更新了。

上面一个看似简单的操作,其实是一个典型的发布订阅模式,公众号属于发布者,用户属于订阅者;用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。

◾ 发布/订阅模式的优点是对象之间解耦,异步编程中,可以更松耦合的代码编写;缺点是创建订阅者本身要消耗一定的时间和内存,虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护。

想要手写实现发布/订阅模式的童鞋可以看我发的这篇文章:从零带你手写一个“发布-订阅者模式“ ,保姆级教学

4. Promise

Promise 是一种处理异步代码(而不会陷入回调地狱)的方式。

多年来,promise 已成为语言的一部分(在 ES2015 中进行了标准化和引入),并且最近变得更加集成,在 ES2017 中具有了 async 和 await。

异步函数 在底层使用了 promise,因此了解 promise 的工作方式是了解 asyncawait 的基础。

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)

一个 Promise 必然处于以下几种状态之一:

  • 待定 (pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已成功 (fulfilled): 意味着操作成功完成。
  • 已拒绝 (rejected): 意味着操作失败。

当 promise 被调用后,它会以处理中状态 (pending) 开始。 这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。

被创建的 promise 最终会以被解决状态 (fulfilled)被拒绝状态 (rejected) 结束,并在完成时调用相应的回调函数(传给 thencatch)。

● Promise 的链式调用

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
复制代码

上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function (comments) {
  console.log("resolved: ", comments);
}, function (err){
  console.log("rejected: ", err);
});
复制代码

上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。

如果采用箭头函数,上面的代码可以写得更简洁。

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);
复制代码

如果想要更详细的学习 Promise ,可以参考我发的这几篇文章:

5. async/await

asyncawait 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 的一部分,简单来说,它们是基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。

Wenn Sie mehr über async/await erfahren möchten, können Sie sich auf diesen Artikel beziehen, den ich gepostet habe:

Für vollständigere und detailliertere Inhalte in hoher Qualität klicken Sie hier, um sie anzuzeigen

beziehen auf

Ich denke du magst

Origin juejin.im/post/7067891660545327111
Empfohlen
Rangfolge