搜尋
首頁web前端js教程深入理解JavaScript程式設計中的同步與非同步機制_基礎知識

 JavaScript的優點之一是其如何處理非同步程式碼。非同步程式碼會被放入事件佇列,等到所有其他程式碼執行後才進行,而不會阻塞執行緒。然而,對於初學者來說,書寫非同步程式碼可能會比較困難。而在這篇文章裡,我將會消除你可能會有的任何困惑。
理解非同步程式碼

JavaScript最基礎的非同步函數是setTimeout和setInterval。 setTimeout會在一定時間後執行給定的函數。它接受一個回調函數作為第一參數和一個毫秒時間作為第二參數。以下是用法舉例:
 

console.log( "a" );
setTimeout(function() {
  console.log( "c" )
}, 500 );
setTimeout(function() {
  console.log( "d" )
}, 500 );
setTimeout(function() {
  console.log( "e" )
}, 500 );
console.log( "b" );

如預期,控制台先輸出“a”、“b”,大約500毫秒後,再看到“c”、“d”、“e”。我用「大約」是因為setTimeout事實上是不可預測的。實際上,甚至 HTML5規範都提到了這個問題:

  •     「這個API無法保證計時會如期準確地運作。由於CPU負載、其他任務等所導致的延遲是可以​​預料到的。」


有趣的是,直到在同一程式段中所有其餘的程式碼執行結束後,逾時才會發生。所以如果設定了逾時,同時執行了需要長時間運行的函數,那麼在該函數執行完成之前,超時甚至不會啟動。實際上,非同步函數,如setTimeout和setInterval,被壓入了稱為Event Loop的佇列。

Event Loop是回呼函數佇列。當非同步函數執行時,回呼函數會被壓入這個佇列。 JavaScript引擎直到非同步函數執行完成後,才會開始處理事件循環。這意味著JavaScript程式碼不是多執行緒的,即使表現的行為相似。事件循環是先進先出(FIFO)佇列,這說明回呼是按照它們被加入佇列的順序執行的。 JavaScript被 node選做為開發語言,就是因為寫這樣的程式碼多麼簡單。

Ajax

非同步Javascript與XML(AJAX)永久性的改變了Javascript語言的狀況。突然間,瀏覽器不再需要重新載入即可更新web頁面。 在不同的瀏覽器中實作Ajax的程式碼可能漫長且乏味;但是,幸虧有jQuery(還有其他函式庫)的幫助,我們能夠以很容易並且優雅的方式實作客戶端-伺服器端通訊。

我們可以使用jQuery跨瀏覽器介面$.ajax很容易地檢索數據,然而卻不能呈現幕後發生了什麼。如:

var data;
$.ajax({
  url: "some/url/1",
  success: function( data ) {
    // But, this will!
    console.log( data );
  }
})
// Oops, this won't work...
console.log( data );

較容易犯的錯誤,是在呼叫$.ajax之後馬上使用data,但實際上是這樣的:
 

xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
  if ( xmlhttp.readyState === 4 ) {
    console.log( data );
  }
};
xmlhttp.send( null );

底層的XmlHttpRequest物件發起請求,設定回呼函數用來處理XHR的readystatechnage事件。然後執行XHR的send方法。在XHR運作中,當其屬性readyState改變時readystatechange事件就會被觸發,只有在XHR從遠端伺服器接收回應結束時回呼函數才會觸發執行。

處理非同步程式碼

非同步程式設計很容易陷入我們常說的「回調地獄」。因為事實上幾乎JS中的所有非同步函數都用到了回調,連續執行幾個非同步函數的結果就是層層嵌套的回呼函數以及隨之而來的複雜程式碼。

node.js中的許多函數也是非同步的。因此如下的程式碼基本上很常見:
 

var fs = require( "fs" );
fs.exists( "index.js", function() {
  fs.readFile( "index.js", "utf8", function( err, contents ) {
    contents = someFunction( contents ); // do something with contents
    fs.writeFile( "index.js", "utf8", function() {
      console.log( "whew! Done finally..." );
    });
  });
});
console.log( "executing..." );

下面的客戶端程式碼也很多見:

 

GMaps.geocode({
  address: fromAddress,
  callback: function( results, status ) {
    if ( status == "OK" ) {
      fromLatLng = results[0].geometry.location;
      GMaps.geocode({
        address: toAddress,
        callback: function( results, status ) {
          if ( status == "OK" ) {
            toLatLng = results[0].geometry.location;
            map.getRoutes({
              origin: [ fromLatLng.lat(), fromLatLng.lng() ],
              destination: [ toLatLng.lat(), toLatLng.lng() ],
              travelMode: "driving",
              unitSystem: "imperial",
              callback: function( e ){
                console.log( "ANNNND FINALLY here's the directions..." );
                // do something with e
              }
            });
          }
        }
      });
    }
  }
});

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

嵌套的回調很容易帶來代碼中的“壞味道”,不過你可以用以下的幾種風格來嘗試解決這個問題

  •     The problem isn't with the language itself; it's with the way programmers use the language — Async Javascript.

    沒有糟糕的語言,只有糟糕的程式猿 ——非同步JavaSript


命名函數

清除巢狀回呼的一個方便的解決方案是簡單的避免雙層以上的巢狀。傳遞一個命名函數給作為回調參數,而不是傳遞匿名函數:
 

var fromLatLng, toLatLng;
var routeDone = function( e ){
  console.log( "ANNNND FINALLY here's the directions..." );
  // do something with e
};
var toAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    toLatLng = results[0].geometry.location;
    map.getRoutes({
      origin: [ fromLatLng.lat(), fromLatLng.lng() ],
      destination: [ toLatLng.lat(), toLatLng.lng() ],
      travelMode: "driving",
      unitSystem: "imperial",
      callback: routeDone
    });
  }
};
var fromAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    fromLatLng = results[0].geometry.location;
    GMaps.geocode({
      address: toAddress,
      callback: toAddressDone
    });
  }
};
GMaps.geocode({
  address: fromAddress,
  callback: fromAddressDone
});

此外, async.js 函式庫可以幫助我們處理多重Ajax requests/responses. 例如:
 

async.parallel([
  function( done ) {
    GMaps.geocode({
      address: toAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  },
  function( done ) {
    GMaps.geocode({
      address: fromAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  }
], function( errors, results ) {
  getRoute( results[0], results[1] );
});

这段代码执行两个异步函数,每个函数都接收一个名为"done"的回调函数并在函数结束的时候调用它。当两个"done"回调函数结束后,parallel函数的回调函数被调用并执行或处理这两个异步函数产生的结果或错误。

Promises模型
引自 CommonJS/A:

  •     promise表示一个操作独立完成后返回的最终结果。

有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promise API。jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。
 

var geocode = function( address ) {
  var dfd = new $.Deferred();
  GMaps.geocode({
    address: address,
    callback: function( response, status ) {
      return dfd.resolve( response );
    }
  });
  return dfd.promise();
};
var getRoute = function( fromLatLng, toLatLng ) {
  var dfd = new $.Deferred();
  map.getRoutes({
    origin: [ fromLatLng.lat(), fromLatLng.lng() ],
    destination: [ toLatLng.lat(), toLatLng.lng() ],
    travelMode: "driving",
    unitSystem: "imperial",
    callback: function( e ) {
      return dfd.resolve( e );
    }
  });
  return dfd.promise();
};
var doSomethingCoolWithDirections = function( route ) {
  // do something with route
};
$.when( geocode( fromAddress ), geocode( toAddress ) ).
  then(function( fromLatLng, toLatLng ) {
    getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
  });

这允许你执行两个异步函数后,等待它们的结果,之后再用先前两个调用的结果来执行另外一个函数。

  •     promise表示一个操作独立完成后返回的最终结果。

在这段代码里,geocode方法执行了两次并返回了一个promise。异步函数之后执行,并在其回调里调用了resolve。然后,一旦两次调用resolve完成,then将会执行,其接收了之前两次调用geocode的返回结果。结果之后被传入getRoute,此方法也返回一个promise。最终,当getRoute的promise解决后,doSomethingCoolWithDirections回调就执行了。
 
事件
事件是另一种当异步回调完成处理后的通讯方式。一个对象可以成为发射器并派发事件,而另外的对象则监听这些事件。这种类型的事件处理方式称之为 观察者模式 。 backbone.js 库在withBackbone.Events中就创建了这样的功能模块。
 

var SomeModel = Backbone.Model.extend({
  url: "/someurl"
});
var SomeView = Backbone.View.extend({
  initialize: function() {
    this.model.on( "reset", this.render, this );
    this.model.fetch();
  },
  render: function( data ) {
    // do something with data
  }
});
var view = new SomeView({
  model: new SomeModel()
});

还有其他用于发射事件的混合例子和函数库,例如 jQuery Event Emitter , EventEmitter , monologue.js ,以及node.js内建的 EventEmitter 模块。

  •     事件循环是一个回调函数的队列。

一个类似的派发消息的方式称为 中介者模式 , postal.js 库中用的即是这种方式。在中介者模式,有一个用于所有对象监听和派发事件的中间人。在这种模式下,一个对象不与另外的对象产生直接联系,从而使得对象间都互相分离。

绝不要返回promise到一个公用的API。这不仅关系到了API用户对promises的使用,也使得重构更加困难。不过,内部用途的promises和外部接口的事件的结合,却可以让应用更低耦合且便于测试。

在先前的例子里面,doSomethingCoolWithDirections回调函数在两个geocode函数完成后执行。然后,doSomethingCoolWithDirections才会获得从getRoute接收到的响应,再将其作为消息发送出去。
 

var doSomethingCoolWithDirections = function( route ) {
  postal.channel( "ui" ).publish( "directions.done", {
    route: route
  });
};

这允许了应用的其他部分不需要直接引用产生请求的对象,就可以响应异步回调。而在取得命令时,很可能页面的好多区域都需要更新。在一个典型的jQuery Ajax过程中,当接收到的命令变化时,要顺利的回调可能就得做相应的调整了。这可能会使得代码难以维护,但通过使用消息,处理UI多个区域的更新就会简单得多了。
 

var UI = function() {
  this.channel = postal.channel( "ui" );
  this.channel.subscribe( "directions.done", this.updateDirections ).withContext( this );
};
UI.prototype.updateDirections = function( data ) {
  // The route is available on data.route, now just update the UI
};
app.ui = new UI();

另外一些基于中介者模式传送消息的库有 amplify, PubSubJS, and radio.js。

结论

JavaScript 使得编写异步代码很容易. 使用 promises, 事件, 或者命名函数来避免“callback hell”. 为获取更多javascript异步编程信息,请点击Async JavaScript: Build More Responsive Apps with Less . 更多的实例托管在github上,地址NetTutsAsyncJS,赶快Clone吧 !

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript應用程序:從前端到後端JavaScript應用程序:從前端到後端May 04, 2025 am 12:12 AM

JavaScript可用於前端和後端開發。前端通過DOM操作增強用戶體驗,後端通過Node.js處理服務器任務。 1.前端示例:改變網頁文本內容。 2.後端示例:創建Node.js服務器。

Python vs. JavaScript:您應該學到哪種語言?Python vs. JavaScript:您應該學到哪種語言?May 03, 2025 am 12:10 AM

選擇Python還是JavaScript應基於職業發展、學習曲線和生態系統:1)職業發展:Python適合數據科學和後端開發,JavaScript適合前端和全棧開發。 2)學習曲線:Python語法簡潔,適合初學者;JavaScript語法靈活。 3)生態系統:Python有豐富的科學計算庫,JavaScript有強大的前端框架。

JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

node.js流帶打字稿node.js流帶打字稿Apr 30, 2025 am 08:22 AM

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python vs. JavaScript:性能和效率注意事項Python vs. JavaScript:性能和效率注意事項Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript的起源:探索其實施語言JavaScript的起源:探索其實施語言Apr 29, 2025 am 12:51 AM

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

幕後:什麼語言能力JavaScript?幕後:什麼語言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。