javascript異步變遷(一)

你可能知道,javascript在瀏覽器端是一門單線程的語言(single thread)。道理很簡單,就是設(shè)計者當時設(shè)計這門語言的時候只是考慮到這門語言就是一個腳本語言,應該足夠簡單,要易于上手。

隊列

  但是慢慢的發(fā)展就會產(chǎn)生一個問題,單線程一次只能執(zhí)行一個任務(wù),如果有多個任務(wù)同時執(zhí)行,就必須排隊,前面一個任務(wù)完成才能執(zhí)行后面一個任務(wù),以此類推。
  這種模式下你不用考慮復雜的線程安全,也不用考慮線程通信,故出發(fā)點是好的。但是有一個壞處,會發(fā)生線程阻塞,就是一個耗時很長的任務(wù)如果沒有執(zhí)行完,就會阻塞之后任務(wù)的執(zhí)行。通俗的講就是會造成瀏覽器的假死
  為了解決這個問題,Javascript語言將任務(wù)的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。

  • "同步模式"就是上一段的模式,后一個任務(wù)等待前一個任務(wù)結(jié)束,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的;"異步模式"則完全不同,每一個任務(wù)有一個或多個回調(diào)函數(shù)(callback),前一個任務(wù)結(jié)束后,不是執(zhí)行后一個任務(wù),而是執(zhí)行回調(diào)函數(shù),后一個任務(wù)則是不等前一個任務(wù)結(jié)束就執(zhí)行,所以程序的執(zhí)行順序與任務(wù)的排列順序是不一致的、異步的。
  • "異步模式"非常重要。在瀏覽器端,耗時很長的操作都應該異步執(zhí)行,避免瀏覽器失去響應,最好的例子就是Ajax操作。在服務(wù)器端,"異步模式"甚至是唯一的模式,因為執(zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有http請求,服務(wù)器性能會急劇下降,很快就會失去響應。
    本文總結(jié)了"異步模式"編程的4種方法,理解它們可以讓你寫出結(jié)構(gòu)更合理、性能更出色、維護更方便的Javascript程序。
異步

提示:異步不代表加快執(zhí)行,通常我們說的異步是異步I/O,我們知道I/O操作遠沒有CPU的處理速度快。當我們?nèi)フ埱笠粋€HTTP服務(wù)、查詢一段SQL,讀取一個文件的時候,會出現(xiàn)CPU占用不完全的情況,這個時候CPU必須要等待I/O操作完成,這個等待時間是不定的,就會導致CPU資源浪費。
合理的解決辦法就是采用異步操作,當處于I/O等待狀態(tài)就將CPU轉(zhuǎn)換到其它任務(wù)當中,I/O結(jié)束等待后就重新獲得該方法的控制權(quán)繼續(xù)執(zhí)行。


回調(diào)函數(shù)

JavaScript語言對異步編程的實現(xiàn),就是回調(diào)函數(shù)。所謂回調(diào)函數(shù),就是把任務(wù)的第二段單獨寫在一個函數(shù)里面,等到重新執(zhí)行這個任務(wù)的時候,就直接調(diào)用這個函數(shù)。它的英語名字callback,直譯過來就是"重新調(diào)用"。

先看一個最簡單的回調(diào)

function f1(callback){
    //setTimeout是最簡單的回調(diào)函數(shù),等待一段時間后執(zhí)行
  setTimeout(function () {
  // f1的任務(wù)代碼
  callback();
  }, 1000);
}

當我們執(zhí)行f1這個函數(shù)的時候,我們會加載一個定時器方法,1秒過后,會執(zhí)行它的回調(diào)函數(shù)
讀取文件進行處理,是這樣寫的。

 fs.readFile('/etc/passwd', function (err, data) {
     if (err) throw err;
     console.log(data);
 });console.log(data);});

上面代碼讀取了一個文件,回調(diào)函數(shù)有兩個參數(shù),第一個為錯誤對象,第二個為讀取到的數(shù)據(jù)
讓情況稍微復雜一點
看下面這個代碼

 $.post("/ajax/post1",function(data){
    if(data.type=="book"){
        $.post("/ajax/book",function(data1){
            console.log(data1);
        });
    }else if(data.type=="article"){
        $.post("/ajax/article",function(data2){
            console.log(data2);
        });
    }
});

這個例子先請求了一個服務(wù),根據(jù)返回的結(jié)果,判斷它是哪一種狀態(tài),在根據(jù)它的狀態(tài)又請求了一次服務(wù)。這個例子看起來還比較好受,我們在看下面這種狀態(tài),是不是大家都很熟悉。

                    });
                  });
                });
              });
            });
          });
        });
      });
    });
  });
});

上面這段代碼就是javascript著名的回調(diào)陷阱,多次的嵌套回調(diào)會讓你的代碼亂成一團,并且難以維護
所以機智的開發(fā)人員為了解決這個問題,他們采取了很多種辦法,咱們從簡單的開始分析

異步函數(shù)式類庫

  • Async.js
  • When.js
  • parallel.js
  • ...

上面這些庫有一個共同點,就是通過函數(shù)的方式把異步方法嵌套在了一起,其實異步的本質(zhì)還是沒有改變,他們都基于Promises/A規(guī)范。
而且這些庫還有一個共通點,支持同步方法的隊列執(zhí)行,這個在js動畫、集合處理等用的比較多,這里不多闡述。
我們這里拿When.js來講解,首先,我們看一段代碼:

var getData = function(callback) {
    $.getJSON(api, function(data){
        callback(data[0]);
    });
}
var getImg = function(src, callback) {
    var img = new Image();
    img.onload = function() {
        callback(img);
    };
    img.src = src;
}
var showImg = function(img) {
    $(img).appendTo($('#container'));
}
getData(function(data) {
    getImg(data, function(img) {
        showImg(img);
    });
});

這段代碼完成了三個任務(wù):1)獲取數(shù)據(jù);2)加載圖片;3)顯示圖片,其中,任務(wù)1和2是異步,3是同步,使用的是最常見的callback機制來處理異步邏輯,好處是淺顯易懂,缺點是強耦合、不直觀、處理異常麻煩等等。
另外一種常見的實現(xiàn)異步編程的方案是事件監(jiān)聽器,例如使用QWrap的CustEvent,讓任務(wù)成功時fireEvent,那么注冊了這個Event的監(jiān)聽器就可以收到這個事件,并收到事件傳遞過來的數(shù)據(jù),Dom標準事件也是采用的這種形式。這種方案也很好理解,代碼從略。事件監(jiān)聽可以解耦,可以綁定任意多個監(jiān)聽器,但是依然不直觀,而且事件發(fā)生之后再綁定的監(jiān)聽器也得不到觸發(fā)。

我們嘗試用when.js改寫下這段代碼

var getData = function () {
      var deferred = when.defer();

      $.getJSON(api, function (data) {
          deferred.resolve(data[0]);
      });

      return deferred.promise;
}
var getImg = function (src) {
      var deferred = when.defer();
      var img = new Image();
      img.onload = function () {
          deferred.resolve(img);
      };
      img.src = src;
      return deferred.promise;
}
var showImg = function (img) {
      $(img).appendTo($('#container'));
}
getData()
      .then(getImg)
      .then(showImg);

看最后三行代碼,是不是一目了然,非常的語義化?
他解決了一個問題,就是我們前面提到的嵌套陷阱!
OK!下一章讓我們來看一下Whenjs還有什么功能,當然這里的重點不是這個庫,大家感興趣的話可以自己去看看when.js,我們的重點是Promises/A規(guī)范

下一章我們繼續(xù)

Promises/A規(guī)范詳細闡述說明

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,410評論 5 22
  • 歡迎閱讀專門探索 JavaScript 及其構(gòu)建組件的系列文章的第四章。 在識別和描述核心元素的過程中,我們還分享...
    OSC開源社區(qū)閱讀 1,223評論 1 10
  • 2017年5月19日 星期五 天氣晴 崔笑媽媽親子日記 用心做一件事才有可能能把一件事情做的很好!但是...
    崔笑媽媽閱讀 287評論 1 2
  • 活在他的世界里,我沒了自我,眼里只有他,擔心某一天,他有外遇,社會的誘惑那么大,他外遇了,我怎么辦?我總是這樣...
    Cuilyling閱讀 212評論 0 0

友情鏈接更多精彩內(nèi)容