您好,登錄后才能下訂單哦!
本文實例講述了基于javascript的異步編程。分享給大家供大家參考,具體如下:
異步函數這個術語有點名不副實,調用一個函數后,程序只在該函數返回后才能繼續。JavaScript程序員如果稱一個函數為異步的,其意思就是這個函數會導致將來再運行另一個函數,后者取自于事件隊列。如果后面這個函數是作為參數傳遞給前者的,則稱其為回調函數。
callback
回調函數是異步編程最基本的方式。
采用這種方式,我們把同步操作變成了異步操作,主函數不會堵塞程序運行,相當于先執行程序的主要邏輯,將耗時的操作推遲執行。
回調函數的優點是簡單、容易理解和部署,缺點是不利于代碼的閱讀和維護。
我們定義一個delay函數,它是異步的,也就是說它會拖延指定函數的執行,從而使現在正在執行的程序繼續執行。delay函數如下:
function delay(time, callback) { setTimeout(function () { callback("Slept for "+time); }, time); }
那么如果我要delay兩次呢?
delay(1000, function(msg) { console.log(msg); delay(1200, function (msg) { console.log(msg); } }) //...waits 1000ms // > "Slept for 1000" //...waits another 1200ms // > "Slept for 1200"
只有這樣我們才能夠確保兩個delay一個接一個的執行。如果層次多了呢?就會形成回調地獄。當異步任務很多時,維護大量的callback將是一場災難。
Promise
Promise是一個被納入ES6中的規范,各大框架也早已實現相關方法。
Promise可以理解為一個承諾,如果A調用B,B返回一個承諾給A,然后A就可以在寫計劃的時候這么寫,當B返回結果的時候,A就執行方案1,如果B沒有返回A要的結果,A就執行方案2。這樣一來,所有的潛在風險就都在A的可控范圍之內了。
我們看看ES6中promise的用法示例:
'use strict'; var promiseCount = 0; function testPromise() { var thisPromiseCount = ++promiseCount; var log = document.getElementById('log'); log.insertAdjacentHTML('beforeend', thisPromiseCount +') Started (<small>Sync code started</small>)<br/>'); // 我們創建一個新的Promise,期望3秒后得到結果 var p1 = new Promise( //當Promise解決或拒絕時該函數被調用 function(resolve, reject) { log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise started (<small>Async code started</small>)<br/>'); // 創建異步操作 window.setTimeout( function() { // 滿足Promise resolve(thisPromiseCount); }, Math.random() * 2000 + 1000); }); // 當Promise被滿足時執行 p1.then( // 輸出信息和值 function(val) { log.insertAdjacentHTML('beforeend', val +') Promise fulfilled (<small>Async code terminated</small>)<br/>'); }) .catch( // 當Promise被拒絕時執行 function(reason) { console.log('Handle rejected promise ('+reason+') here.'); }); log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise made (<small>Sync code terminated</small>)<br/>'); }
快速連續執行函數,得到結果:
說明1,2異步操作后正常順序執行完畢。更多Promise的詳細用法請參考:MDN
很多框架也提供了Promise相關方法,這里我們以jQuery為例。
$("button").bind( "click", function() { $("p").append( "Started..."); $("div").each(function( i ) { $( this ).fadeIn().fadeOut( 1000 * (i+1) ); }); $( "div" ).promise().done(function() { $( "p" ).append( " Finished! " ); }); });
可以看到,當$("div")的所有任務執行完畢后,就會調用最后的done操作。
Jquery中的Promise也可以代表多種結果,出現不同結果時會分別調用相應的回調。我們以ajax調用為例。1.5之前版本中,代碼必須寫成這樣:
$.get('/getdata',{ success:onSuccess, failure:onFailure, always:onAlways });
而1.5+版本引入了Promise對象后。可以寫成如下形式:
var promise = $.get('/getdata'); promise.done(onSuccess); promise.fail(onFailure); promise.always(onAlways);
那么這種變化有什么好處呢?為什么非要在觸發ajax調用之后再附加回調呢?如果ajax要實現很多效果,比如觸發動畫、插入html、鎖定輸入等,那么僅僅由負責發出請求那部分應用代碼來處理所有這些效果,顯然很蠢。只傳遞Promise就顯得很優雅。
更多詳細請參考:jquery
Promise雖然是很優雅,但是也只是解決了回調地獄的問題,真正簡化javascript異步編程的還是Generator。
Generator
生成器是ES6中的語法。
何為生成器?讓我們先看看以下代碼:
function* quips(name) { yield "hello " + name + "!"; yield "i hope you are enjoying the blog posts"; if (name.startsWith("X")) { yield "it's cool how your name starts with X, " + name; } yield "see you later!"; }
你沒有看錯,這就是javascript代碼。是不是和你曾經認識的javascript不太一樣。這個函數就叫做生成器函數。生成器函數看起來和普通的函數是不是有點相像呢?
它們的區別如下:
一般的函數以function開頭,而生成器函數以function* 開頭。
生成器函數中有一個特殊關鍵字就是yield,作用就是暫停函數。配合next方法來調用可以達到一步一步的執行函數的目的。
我們看看next方法的使用:
> var iter = quips("jorendorff"); [object Generator] > iter.next() { value: "hello jorendorff!", done: false } > iter.next() { value: "i hope you are enjoying the blog posts", done: false } > iter.next() { value: "see you later!", done: false } > iter.next() { value: undefined, done: true }
可以看到,每一次next方法后,生成器函數就執行到下一個yield位置處。
下面我們講解如何通過生成器函數來取代回調函數。我們還是以一開始的多次延遲delay為例。
首先,我們需要定義一個生成器:
function* myDelayedMessages() { /* delay 1000 ms and print the result */ /* delay 1200 ms and print the result */ }
然后我們需要設置delay時間來執行指定操作,我們現在暫定為空函數。
function* myDelayedMessages() { console.log(delay(1000, function(){})); console.log(delay(1200, function(){})); }
然后我們使用yield關鍵字:
function* myDelayedMessages() { console.log(yield delay(1000, function(){})); console.log(yield delay(1200, function(){})); }
然后我們就需要指定一個函數run來調用生成器函數的next方法,并且將空函數改為參數resume:
function run(generatorFunction) { var generatorItr = generatorFunction(resume); function resume(callbackValue) { generatorItr.next(callbackValue); } generatorItr.next() }
最后執行代碼如下:
run(function* myDelayedMessages(resume) { console.log(yield delay(1000, resume)); console.log(yield delay(1200, resume)); }) //...waits 1000ms // > "Slept for 1000" //...waits 1200ms // > "Slept for 1200"
這樣就完美的避免了回調地獄噢!
更多關于JavaScript相關內容可查看本站專題:《javascript面向對象入門教程》、《JavaScript中json操作技巧總結》、《JavaScript切換特效與技巧總結》、《JavaScript查找算法技巧總結》、《JavaScript動畫特效與技巧匯總》、《JavaScript錯誤與調試技巧總結》、《JavaScript數據結構與算法技巧總結》、《JavaScript遍歷算法與技巧總結》及《JavaScript數學運算用法總結》
希望本文所述對大家JavaScript程序設計有所幫助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。