溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Javascript異步編程簡(jiǎn)介

發(fā)布時(shí)間:2021-08-30 20:52:18 來(lái)源:億速云 閱讀:139 作者:chen 欄目:web開(kāi)發(fā)

這篇文章主要介紹“Javascript異步編程簡(jiǎn)介”,在日常操作中,相信很多人在Javascript異步編程簡(jiǎn)介問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Javascript異步編程簡(jiǎn)介”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

JavaScript 異步編程簡(jiǎn)介

回調(diào)函數(shù)和異步執(zhí)行

所謂的異步指的是函數(shù)的調(diào)用并不直接返回執(zhí)行的結(jié)果,而往往是通過(guò)回調(diào)函數(shù)異步的執(zhí)行。

我們先看看回調(diào)函數(shù)是什么:

var fn = function(callback) {      // do something here      ...      callback.apply(this, para);  };   var mycallback = function(parameter) {      // do someting in customer callback  };   // call the fn with callback as parameter  fn(mycallback);

回調(diào)函數(shù),其實(shí)就是調(diào)用用戶提供的函數(shù),該函數(shù)往往是以參數(shù)的形式提供的?;卣{(diào)函數(shù)并不一定是異步執(zhí)行的。比如上述的例子中,回調(diào)函數(shù)是被同步執(zhí)行的。大部分語(yǔ)言都支持回調(diào),C++可用通過(guò)函數(shù)指針或者回調(diào)對(duì)象,Java一般也是使用回調(diào)對(duì)象。

在Javascript中有很多通過(guò)回調(diào)函數(shù)來(lái)執(zhí)行的異步調(diào)用,例如setTimeout()或者setInterval()。

setTimeout(function(){      console.log("this will be exectued after 1 second!");  },1000);

在以上的例子中,setTimeout直接返回,匿名函數(shù)會(huì)在1000毫秒(不一定能保證是1000毫秒)后異步觸發(fā)并執(zhí)行,完成打印控制臺(tái)的操作。也就是說(shuō)在異步操作的情境下,函數(shù)直接返回,把控制權(quán)交給回調(diào)函數(shù),回調(diào)函數(shù)會(huì)在以后的某一個(gè)時(shí)間片被調(diào)度執(zhí)行。那么為什么需要異步呢?為什么不能直接在當(dāng)前函數(shù)中完成操作呢?這就需要了解Javascript的線程模型了。

Javascript線程模型和事件驅(qū)動(dòng)

Javascript最初是被設(shè)計(jì)成在瀏覽器中輔助提供HTML的交互功能。在瀏覽器中都包含一個(gè)Javascript引擎,Javscript程序就運(yùn)行在這個(gè)引擎之中,并且只有一個(gè)線程。單線程能都帶來(lái)很多優(yōu)點(diǎn),程序員們可以很開(kāi)心的不用去考慮諸如資源同步,死鎖等多線程阻塞式編程所需要面對(duì)的惱人的問(wèn)題。但是很多人會(huì)問(wèn),既然Javascript是單線程的,那它又如何能夠異步的執(zhí)行呢?

這就需要了解到Javascript在瀏覽器中的事件驅(qū)動(dòng)(event driven)機(jī)制。事件驅(qū)動(dòng)一般通過(guò)事件循環(huán)(event loop)和事件隊(duì)列(event queue)來(lái)實(shí)現(xiàn)的。假定瀏覽器中有一個(gè)專門(mén)用于事件調(diào)度的實(shí)例(該實(shí)例可以是一個(gè)線程,我們可以稱之為事件分發(fā)線程event dispatch thread),該實(shí)例的工作就是一個(gè)不結(jié)束的循環(huán),從事件隊(duì)列中取出事件,處理所有很事件關(guān)聯(lián)的回調(diào)函數(shù)(event handler)。注意回調(diào)函數(shù)是在Javascript的主線程中運(yùn)行的,而非事件分發(fā)線程中,以保證事件處理不會(huì)發(fā)生阻塞。

Event Loop Code:

while(true) {   var event = eventQueue.pop();   if(event && event.handler) {       event.handler.execute(); // execute the callback in Javascript thread   } else {       sleep(); //sleep some time to release the CPU do other stuff   }  }

通過(guò)事件驅(qū)動(dòng)機(jī)制,我們可以想象Javascript的編程模型就是響應(yīng)一系列的事件,執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。很多UI框架都采用這樣的模型(例如Java Swing)。

那為什要異步呢,同步不是很好么?

異步的主要目的是處理非阻塞,在和HTML交互的過(guò)程中,會(huì)需要一些IO操作(典型的就是Ajax請(qǐng)求,腳本文件加載),如果這些操作是同步的,就會(huì)阻塞其它操作,用戶的體驗(yàn)就是頁(yè)面失去了響應(yīng)。

綜上所述Javascript通過(guò)事件驅(qū)動(dòng)機(jī)制,在單線程模型下,以異步回調(diào)函數(shù)的形式來(lái)實(shí)現(xiàn)非阻塞的IO操作。

Javascript異步編程帶來(lái)的挑戰(zhàn)

Javascript的單線程模型有很多好處,但同時(shí)也帶來(lái)了很多挑戰(zhàn)。

代碼可讀性

想象一下,如果某個(gè)操作需要經(jīng)過(guò)多個(gè)非阻塞的IO操作,每一個(gè)結(jié)果都是通過(guò)回調(diào),程序有可能會(huì)看上去像這個(gè)樣子。

operation1(function(err, result) {      operation2(function(err, result) {          operation3(function(err, result) {              operation4(function(err, result) {                  operation5(function(err, result) {                      // do something useful                  })              })          })      })  })

我們稱之為意大利面條式(spaghetti)的代碼。這樣的代碼很難維護(hù)。這樣的情況更多的會(huì)發(fā)生在server side的情況下。

流程控制

異步帶來(lái)的另一個(gè)問(wèn)題是流程控制,舉個(gè)例子,我要訪問(wèn)三個(gè)網(wǎng)站的內(nèi)容,當(dāng)三個(gè)網(wǎng)站的內(nèi)容都得到后,合并處理,然后發(fā)給后臺(tái)。代碼可以這樣寫(xiě):

var urls = ['url1','url2','url3'];  var result = [];   for (var i = 0, len = urls.length(); i < len; i++ ) {      $.ajax({          url: urls[i],          context: document.body,          success: function(){            //do something on success            result.push("one of the request done successfully");            if (result.length === urls.length()) {                //do something when all the request is completed successfully            }          }});  }

上述代碼通過(guò)檢查result的長(zhǎng)度的方式來(lái)決定是否所有的請(qǐng)求都處理完成,這是一個(gè)很丑陋方法,也很不可靠。

異常和錯(cuò)誤處理

通過(guò)上一個(gè)例子,我們還可以看出,為了使程序更健壯,我們還需要加入異常處理。 在異步的方式下,異常處理分布在不同的回調(diào)函數(shù)中,我們無(wú)法在調(diào)用的時(shí)候通過(guò)try...catch的方式來(lái)處理異常, 所以很難做到有效,清楚。

更好的Javascript異步編程方式
 

“這是***的時(shí)代,也是最糟糕的時(shí)代”

為了解決Javascript異步編程帶來(lái)的問(wèn)題,很多的開(kāi)發(fā)者做出了不同程度的努力,提供了很多不同的解決方案。然而面對(duì)如此眾多的方案應(yīng)該如何選擇呢?我們這就來(lái)看看都有哪些可供選擇的方案吧。

Promise

Promise 對(duì)象曾經(jīng)以多種形式存在于很多語(yǔ)言中。這個(gè)詞***由C++工程師用在Xanadu 項(xiàng)目中,Xanadu 項(xiàng)目是Web 應(yīng)用項(xiàng)目的先驅(qū)。隨后Promise 被用在E編程語(yǔ)言中,這又激發(fā)了Python 開(kāi)發(fā)人員的靈感,將它實(shí)現(xiàn)成了Twisted 框架的Deferred 對(duì)象。

2007 年,Promise 趕上了JavaScript 大潮,那時(shí)Dojo 框架剛從Twisted框架汲取靈感,新增了一個(gè)叫做dojo.Deferred 的對(duì)象。也就在那個(gè)時(shí)候,相對(duì)成熟的Dojo 框架與初出茅廬的jQuery 框架激烈地爭(zhēng)奪著人氣和名望。2009 年,Kris Zyp 有感于dojo.Deferred 的影響力提出了CommonJS 之Promises/A 規(guī)范。同年,Node.js ***亮相。

在編程的概念中,future,promise,和delay表示同一個(gè)概念。Promise翻譯成中文是“承諾”,也就是說(shuō)給你一個(gè)東西,我保證未來(lái)能夠做到,但現(xiàn)在什么都沒(méi)有。它用來(lái)表示異步操作返回的一個(gè)對(duì)象,該對(duì)象是用來(lái)獲取未來(lái)的執(zhí)行結(jié)果的一個(gè)代理,初始值不確定。許多語(yǔ)言都有對(duì)Promise的支持。

Promise的核心是它的then方法,我們可以使用這個(gè)方法從異步操作中得到返回值,或者是異常。then有兩個(gè)可選參數(shù)(有的實(shí)現(xiàn)是三個(gè)),分別處理成功和失敗的情景。

var promise = doSomethingAync()  promise.then(onFulfilled, onRejected)

異步調(diào)用doSomethingAync返回一個(gè)Promise對(duì)象promise,調(diào)用promise的then方法來(lái)處理成功和失敗。這看上去似乎并沒(méi)有很大的改進(jìn)。仍然需要回調(diào)。但是和以前的區(qū)別在于,首先異步操作有了返回值,雖然該值只是一個(gè)對(duì)未來(lái)的承諾;其次通過(guò)使用then,程序員可以有效的控制流程異常處理,決定如何使用這個(gè)來(lái)自未來(lái)的值。

對(duì)于嵌套的異步操作,有了Promise的支持,可以寫(xiě)成這樣的鏈?zhǔn)讲僮鳎?/p>

operation1().then(function (result1) {      return operation2(result1)  }).then(function (result2) {      return operation3(result2);  }).then(function (result3) {      return operation4(result3);  }).then(function (result4) {      return operation5(result4)  }).then(function (result5) {      //And so on  });

Promise提供更便捷的流程控制,例如Promise.all()可以解決需要并發(fā)的執(zhí)行若干個(gè)異步操作,等所有操作完成后進(jìn)行處理。

var p1 = async1();  var p2 = async2();  var p3 = async3();  Promise.all([p1,p2,p3]).then(function(){      // do something when all three asychronized operation finished  });

對(duì)于異常處理,

doA()    .then(doB)    .then(null,function(error){        // error handling here    })

如果doA失敗,它的Promise會(huì)被拒絕,處理鏈上的下一個(gè)onRejected會(huì)被調(diào)用,在這個(gè)例子中就是匿名函數(shù)function(error){}。比起原始的回調(diào)方式,不需要在每一步都對(duì)異常進(jìn)行處理。這生了不少事。

以上只是對(duì)于Promise概念的簡(jiǎn)單陳述,Promise擁有許多不同規(guī)范建議(A,A+,B,KISS,C,D等),名字(Future,Promise,Defer),和開(kāi)源實(shí)現(xiàn)。大家可以參考一下的這些鏈接。

  • jQuery's Deferred Object

  • YUI Promise Class

  • Dojo Promises

  • Q

  • RSVP.js

  • When.js

  • MochiKit.Async

  • FutureJS

  • node-promise

  • WinJS


如果你有選擇困難綜合癥,面對(duì)這么多的開(kāi)源庫(kù)不知道如何決斷,先不要急,這還只是一部分,還有一些庫(kù)沒(méi)有或者不完全采用Promise的概念

Non-Promise

下面列出了其它的一些開(kāi)源的庫(kù),也可以幫助解決Javascript中異步編程所遇到的諸多問(wèn)題,它們的解決方案各不相同,我這里就不一一介紹了。大家有興趣可以去看看或者試用一下。

  • Node-fibers

  • Streamlinejs

  • Step

  • Flow-js

  • Async

  • Async.js

  • slide-flow-control

Non-3rd Party

其實(shí),為了解決Javascript異步編程帶來(lái)的問(wèn)題,不一定非要使用Promise或者其它的開(kāi)源庫(kù),這些庫(kù)提供了很好的模式,但是你也可以通過(guò)有針對(duì)性的設(shè)計(jì)來(lái)解決。

比如,對(duì)于層層回調(diào)的模式,可以利用消息機(jī)制來(lái)改寫(xiě),假定你的系統(tǒng)中已經(jīng)實(shí)現(xiàn)了消息機(jī)制,你的code可以寫(xiě)成這樣:

eventbus.on("init", function(){      operationA(function(err,result){          eventbus.dispatch("ACompleted");      });  });   eventbus.on("ACompleted", function(){      operationB(function(err,result){          eventbus.dispatch("BCompleted");      });  });   eventbus.on("BCompleted", function(){      operationC(function(err,result){          eventbus.dispatch("CCompleted");      });  });   eventbus.on("CCompleted", function(){      // do something when all operation completed  });

這樣我們就把嵌套的異步調(diào)用,改寫(xiě)成了順序執(zhí)行的事件處理。

更多的方式,請(qǐng)大家參考這篇文章,它提出了解決異步的五種模式:回調(diào)、觀察者模式(事件)、消息、Promise和有限狀態(tài)機(jī)(FSM)。

下一代Javscript對(duì)異步編程的增強(qiáng)

ECMAScript6

下一代的Javascript標(biāo)準(zhǔn)Harmony,也就是ECMAScript6正在醞釀中,它提出了許多新的語(yǔ)言特性,比如箭頭函數(shù)、類(Class)、生成器(Generator)、Promise等等。其中Generator和Promise都可以被用于對(duì)異步調(diào)用的增強(qiáng)。

Nodejs的開(kāi)發(fā)版V0.11已經(jīng)可以支持ES6的一些新的特性,使用node --harmony命令來(lái)運(yùn)行對(duì)ES6的支持。

co、Thunk、Koa

koa是由Express原班人馬(主要是TJ)打造,希望提供一個(gè)更精簡(jiǎn)健壯的nodejs框架。koa依賴ES6中的Generator等新特性,所以必須運(yùn)行在相應(yīng)的Nodejs版本上。

利用Generator、co、Thunk,可以在Koa中有效的解決Javascript異步調(diào)用的各種問(wèn)題。

co是一個(gè)異步流程簡(jiǎn)化的工具,它利用Generator把一層層嵌套的調(diào)用變成同步的寫(xiě)法。

var co = require('co');  var fs = require('fs');   var stat = function(path) {    return function(cb){      fs.stat(path,cb);    }  };   var readFile = function(filename) {    return function(cb){      fs.readFile(filename,cb);    }  };   co(function *() {    var stat = yield stat('./README.md');    var content = yield readFile('./README.md');  })();

通過(guò)co可以把異步的fs.readFile當(dāng)成同步一樣調(diào)用,只需要把異步函數(shù)fs.readFile用閉包的方式封裝。

利用Thunk可以進(jìn)一步簡(jiǎn)化為如下的code, 這里Thunk的作用就是用閉包封裝異步函數(shù),返回一個(gè)生成函數(shù)的函數(shù),供生成器來(lái)調(diào)用。

var thunkify = require('thunkify');  var co = require('co');  var fs = require('fs');   var stat = thunkify(fs.stat);  var readFile = thunkify(fs.readFile);   co(function *() {    var stat = yield stat('./README.md');    var content = yield readFile('./README.md');  })();

利用co可以串行或者并行的執(zhí)行異步調(diào)用。

串行

co(function *() {    var a = yield request(a);    var b = yield request(b);  })();

并行

co(function *() {   var res = yield [request(a), request(b)];  })();

到此,關(guān)于“Javascript異步編程簡(jiǎn)介”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI