我所了解的JavaScript異步編程

Javascript語言將任務的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)

我們知道 javascript語言是單線程機制。所謂單線程就是按次序執(zhí)行,執(zhí)行完一個任務再執(zhí)行下一個。對于瀏覽器來說,也就是無法在渲染頁面的同時執(zhí)行代碼。
??單線程機制的優(yōu)點在于實現(xiàn)起來較為簡單,運行環(huán)境相對簡單。缺點在于,如果中間有任務需要響應時間過長,經常會導致頁面加載錯誤或者瀏覽器無響應的狀況。這就是所謂的“同步模式”,程序執(zhí)行順序與任務排列順序一致。
??對于瀏覽器來說,同步模式效率較低,耗時長的任務都應該使用異步模式;"異步模式"非常重要,在瀏覽器端,耗時很長的操作都應該異步執(zhí)行,避免瀏覽器失去響應,最好的例子就是Ajax操作。而在服務器端,異步模式則是唯一的模式,因為執(zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有http請求,服務器性能會急劇下降,很快就會失去響應。。

異步模式的四種方式:

1.回調函數(shù)callback

所謂回調函數(shù),就是將函數(shù)作為參數(shù)傳到需要回調的函數(shù)內部再執(zhí)行。

  • 典型的例子就是發(fā)送ajax請求。例如:
$.ajax({
    async: false, 
    cache: false,
    dataType: 'json',
    url: "url",
    success: function(data) {
      console.log('success');
    },
    error: function(data) {
     console.log('error');
    }
  })

/*當發(fā)送ajax請求后,等待回應的過程不會堵塞程序運行,耗時的操作相當于延后執(zhí)行。
回調函數(shù)的優(yōu)點在于簡單,容易理解,但是可讀性較差,耦合度較高,不易于維護。*/
  • 假定有兩個函數(shù)f1和f2,后者等待前者的執(zhí)行結果。
f1();
f2();

如果f1是一個很耗時的任務,可以考慮改寫f1,把f2寫成f1的回調函數(shù)。

function f1(callback){
  setTimeout(function () {
  // f1的任務代碼
  callback();
 }, 1000);
}
//執(zhí)行代碼
f1(f2);

采用這種方式,我們把同步操作變成了異步操作,f1不會堵塞程序運行,相當于先執(zhí)行程序的主要邏輯,將耗時的操作推遲執(zhí)行。

回調函數(shù)的優(yōu)點是簡單、容易理解和部署,缺點是不利于代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回調函數(shù)。
2.事件驅動(Event-Driven)

事件驅動,指的是由鼠標和熱鍵的動作引發(fā)的一連串的程序操作。任務的執(zhí)行不取決于代碼的順序,而取決于某個事件是否發(fā)生。
例如,為頁面上的某個綁定click事件;

$('#Btn').onclick(function(){
   console.log('click button');
});

綁定事件相當于在元素上進行監(jiān)聽,是否執(zhí)行注冊的事件代碼取決于事件是否發(fā)生。
??優(yōu)點在于容易理解,一個元素上可以綁定多個事件,有利于實現(xiàn)模塊化;但是缺點在于稱為事件驅動的模型后,流程不清晰。

3.發(fā)布/訂閱

發(fā)布訂閱模式(publish-subscribe pattern)又稱為觀察者模式(Observer pattern)。該模式中,有兩類對象:觀察者和目標對象。目標對象中存在著一份觀察者的列表,當目標對象的狀態(tài)發(fā)生改變時,主動通知觀察者,從而建立一種發(fā)布/訂閱的關系。
??上一節(jié)的"事件驅動",完全可以理解成"信號"。我們假定,存在一個"信號中心",某個任務執(zhí)行完成,就向信號中心"發(fā)布"(publish)一個信號,其他任務可以向信號中心"訂閱"(subscribe)這個信號,從而知道什么時候自己可以開始執(zhí)行。這就叫做"發(fā)布/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。
這個模式有多種實現(xiàn),下面采用的是Ben Alman的Tiny Pub/Sub,這是jQuery的一個插件。
首先,f2向"信號中心"jQuery訂閱"done"信號。

jQuery.subscribe("done", f2);

然后,f1進行如下改寫:

function f1(){
 setTimeout(function () {
   // f1的任務代碼
   jQuery.publish("done");
 }, 1000);
}

jQuery.publish("done")的意思是,f1執(zhí)行完成后,向"信號中心"jQuery發(fā)布"done"信號,從而引發(fā)f2的執(zhí)行。
此外,f2完成執(zhí)行后,也可以取消訂閱(unsubscribe)。

jQuery.unsubscribe("done", f2);

這種方法的性質與"事件監(jiān)聽"類似,但是明顯優(yōu)于后者。因為我們可以通過查看"消息中心",了解存在多少信號、每個信號有多少訂閱者,從而監(jiān)控程序的運行。

4.promise模式

這才是重點,Promises對象是CommonJS工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口簡單說,它的思想是,每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數(shù)。使用Promise對象可以用同步操作的流程寫法來表達異步操作,避免了層層嵌套的異步回調,代碼也更加清晰易懂,方便維護,也可以捕捉異常。
??promise模式在任何時刻都處于以下三種狀態(tài)之一:未完成(unfulfilled)、已完成(resolved)和拒絕(rejected)。以CommonJS Promise/A 標準為例,promise對象上的then方法負責添加針對已完成和拒絕狀態(tài)下的處理函數(shù)。then方法會返回另一個promise對象,以便于形成promise管道,這種返回promise對象的方式能夠支持開發(fā)人員把異步操作串聯(lián)起來,如then(resolvedHandler, rejectedHandler); 。resolvedHandler 回調函數(shù)在promise對象進入完成狀態(tài)時會觸發(fā),并傳遞結果;rejectedHandler函數(shù)會在拒絕狀態(tài)下調用。
??Jquery在1.5的版本中引入了一個新的概念叫Deferred,就是CommonJS promise A標準的一種衍生??梢栽趈Query中創(chuàng)建
$.Deferref的對象。同時也對發(fā)送ajax請求以及數(shù)據(jù)類型有了新的修改,參考JQuery API。
??簡單說,它的思想是,每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數(shù)。比如,f1的回調函數(shù)f2,可以寫成:

f1().then(f2);

f1要進行如下改寫:

 function f1(){
    var dfd = $.Deferred();
    setTimeout(function () {
      // f1的任務代碼
      dfd.resolve();
    }, 500);
    return dfd.promise;
  }
這樣寫的優(yōu)點在于,回調函數(shù)變成了鏈式寫法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以實現(xiàn)許多強大的功能。

比如,指定多個回調函數(shù):

  f1().then(f2).then(f3);

再比如,指定發(fā)生錯誤時的回調函數(shù):

  f1().then(f2).fail(f3);

而且,它還有一個前面三種方法都沒有的好處:如果一個任務已經完成,再添加回調函數(shù),該回調函數(shù)會立即執(zhí)行。所以,你不用擔心是否錯過了某個事件或信號。這種方法的缺點就是編寫和理解,都相對比較難。

  • 在ES 6中原生提供了Promise對象,Promise對象代表了某個未來才會知道結果的事件(一般是一個異步操作),并且這個事件對外提供了統(tǒng)一的API,可供進一步處理。

Promise 對象有以下兩個特點:
1)、對象的狀態(tài)不受外界影響。Promise 對象代表一個異步操作,有三種狀態(tài):Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和 Rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。這也是 Promise 這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

2)、一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結果。Promise 對象的狀態(tài)改變,只有兩種可能:從 Pending 變?yōu)?Resolved 和從 Pending 變?yōu)?Rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結果。就算改變已經發(fā)生了,你再對 Promise 對象添加回調函數(shù),也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結果的。

??有了 Promise 對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數(shù)。此外,Promise 對象提供統(tǒng)一的接口,使得控制異步操作更加容易。

??Promise 也有一些缺點。首先,無法取消 Promise,一旦新建它就會立即執(zhí)行,無法中途取消。其次,如果不設置回調函數(shù),Promise 內部拋出的錯誤,不會反應到外部。第三,當處于 Pending 狀態(tài)時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

一個簡單的demo:

function fn(num) {
  var promise = new Promise(function(resolve, reject) {
     if (/* 異步操作成功 */){
       resolve(value);
     } else {
       reject(error);
   }
});

promise.then(function(value) {
 // success
}, function(value) {
 // failure
});

??Promise 構造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是 resolve 方法和 reject 方法。

??如果異步操作成功,則用 resolve 方法將 Promise 對象的狀態(tài),從「未完成」變?yōu)椤赋晒Α梗磸?pending 變?yōu)?resolved);

??如果異步操作失敗,則用 reject 方法將 Promise 對象的狀態(tài),從「未完成」變?yōu)椤甘 梗磸?pending 變?yōu)?rejected)。

promise 基本的 api

1.Promise.resolve()
2.Promise.reject()
3.Promise.prototype.then()
4.Promise.prototype.catch()
5.Promise.all() // 所有的完成

 var p = Promise.all([p1,p2,p3]);

6.Promise.race() // 競速,完成一個即可

參考:
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容