基于javascript的異步編程實(shí)例詳解
本文實(shí)例講述了基于javascript的異步編程。分享給大家供大家參考,具體如下:
異步函數(shù)這個(gè)術(shù)語(yǔ)有點(diǎn)名不副實(shí),調(diào)用一個(gè)函數(shù)后,程序只在該函數(shù)返回后才能繼續(xù)。JavaScript程序員如果稱一個(gè)函數(shù)為異步的,其意思就是這個(gè)函數(shù)會(huì)導(dǎo)致將來(lái)再運(yùn)行另一個(gè)函數(shù),后者取自于事件隊(duì)列。如果后面這個(gè)函數(shù)是作為參數(shù)傳遞給前者的,則稱其為回調(diào)函數(shù)。
callback
回調(diào)函數(shù)是異步編程最基本的方式。
采用這種方式,我們把同步操作變成了異步操作,主函數(shù)不會(huì)堵塞程序運(yùn)行,相當(dāng)于先執(zhí)行程序的主要邏輯,將耗時(shí)的操作推遲執(zhí)行。
回調(diào)函數(shù)的優(yōu)點(diǎn)是簡(jiǎn)單、容易理解和部署,缺點(diǎn)是不利于代碼的閱讀和維護(hù)。
我們定義一個(gè)delay函數(shù),它是異步的,也就是說(shuō)它會(huì)拖延指定函數(shù)的執(zhí)行,從而使現(xiàn)在正在執(zhí)行的程序繼續(xù)執(zhí)行。delay函數(shù)如下:
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"
只有這樣我們才能夠確保兩個(gè)delay一個(gè)接一個(gè)的執(zhí)行。如果層次多了呢?就會(huì)形成回調(diào)地獄。當(dāng)異步任務(wù)很多時(shí),維護(hù)大量的callback將是一場(chǎng)災(zāi)難。
Promise
Promise是一個(gè)被納入ES6中的規(guī)范,各大框架也早已實(shí)現(xiàn)相關(guān)方法。
Promise可以理解為一個(gè)承諾,如果A調(diào)用B,B返回一個(gè)承諾給A,然后A就可以在寫(xiě)計(jì)劃的時(shí)候這么寫(xiě),當(dāng)B返回結(jié)果的時(shí)候,A就執(zhí)行方案1,如果B沒(méi)有返回A要的結(jié)果,A就執(zhí)行方案2。這樣一來(lái),所有的潛在風(fēng)險(xiǎn)就都在A的可控范圍之內(nèi)了。
我們看看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/>'); // 我們創(chuàng)建一個(gè)新的Promise,期望3秒后得到結(jié)果 var p1 = new Promise( //當(dāng)Promise解決或拒絕時(shí)該函數(shù)被調(diào)用 function(resolve, reject) { log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise started (<small>Async code started</small>)<br/>'); // 創(chuàng)建異步操作 window.setTimeout( function() { // 滿足Promise resolve(thisPromiseCount); }, Math.random() * 2000 + 1000); }); // 當(dāng)Promise被滿足時(shí)執(zhí)行 p1.then( // 輸出信息和值 function(val) { log.insertAdjacentHTML('beforeend', val +') Promise fulfilled (<small>Async code terminated</small>)<br/>'); }) .catch( // 當(dāng)Promise被拒絕時(shí)執(zhí)行 function(reason) { console.log('Handle rejected promise ('+reason+') here.'); }); log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise made (<small>Sync code terminated</small>)<br/>'); }
快速連續(xù)執(zhí)行函數(shù),得到結(jié)果:
說(shuō)明1,2異步操作后正常順序執(zhí)行完畢。更多Promise的詳細(xì)用法請(qǐng)參考:MDN
很多框架也提供了Promise相關(guān)方法,這里我們以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! " ); }); });
可以看到,當(dāng)$("div")的所有任務(wù)執(zhí)行完畢后,就會(huì)調(diào)用最后的done操作。
Jquery中的Promise也可以代表多種結(jié)果,出現(xiàn)不同結(jié)果時(shí)會(huì)分別調(diào)用相應(yīng)的回調(diào)。我們以ajax調(diào)用為例。1.5之前版本中,代碼必須寫(xiě)成這樣:
$.get('/getdata',{ success:onSuccess, failure:onFailure, always:onAlways });
而1.5+版本引入了Promise對(duì)象后??梢詫?xiě)成如下形式:
var promise = $.get('/getdata'); promise.done(onSuccess); promise.fail(onFailure); promise.always(onAlways);
那么這種變化有什么好處呢?為什么非要在觸發(fā)ajax調(diào)用之后再附加回調(diào)呢?如果ajax要實(shí)現(xiàn)很多效果,比如觸發(fā)動(dòng)畫(huà)、插入html、鎖定輸入等,那么僅僅由負(fù)責(zé)發(fā)出請(qǐng)求那部分應(yīng)用代碼來(lái)處理所有這些效果,顯然很蠢。只傳遞Promise就顯得很優(yōu)雅。
更多詳細(xì)請(qǐng)參考:jquery
Promise雖然是很優(yōu)雅,但是也只是解決了回調(diào)地獄的問(wèn)題,真正簡(jiǎn)化javascript異步編程的還是Generator。
Generator
生成器是ES6中的語(yǔ)法。
何為生成器?讓我們先看看以下代碼:
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!"; }
你沒(méi)有看錯(cuò),這就是javascript代碼。是不是和你曾經(jīng)認(rèn)識(shí)的javascript不太一樣。這個(gè)函數(shù)就叫做生成器函數(shù)。生成器函數(shù)看起來(lái)和普通的函數(shù)是不是有點(diǎn)相像呢?
它們的區(qū)別如下:
一般的函數(shù)以function開(kāi)頭,而生成器函數(shù)以function* 開(kāi)頭。
生成器函數(shù)中有一個(gè)特殊關(guān)鍵字就是yield,作用就是暫停函數(shù)。配合next方法來(lái)調(diào)用可以達(dá)到一步一步的執(zhí)行函數(shù)的目的。
我們看看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方法后,生成器函數(shù)就執(zhí)行到下一個(gè)yield位置處。
下面我們講解如何通過(guò)生成器函數(shù)來(lái)取代回調(diào)函數(shù)。我們還是以一開(kāi)始的多次延遲delay為例。
首先,我們需要定義一個(gè)生成器:
function* myDelayedMessages() { /* delay 1000 ms and print the result */ /* delay 1200 ms and print the result */ }
然后我們需要設(shè)置delay時(shí)間來(lái)執(zhí)行指定操作,我們現(xiàn)在暫定為空函數(shù)。
function* myDelayedMessages() { console.log(delay(1000, function(){})); console.log(delay(1200, function(){})); }
然后我們使用yield關(guān)鍵字:
function* myDelayedMessages() { console.log(yield delay(1000, function(){})); console.log(yield delay(1200, function(){})); }
然后我們就需要指定一個(gè)函數(shù)run來(lái)調(diào)用生成器函數(shù)的next方法,并且將空函數(shù)改為參數(shù)resume:
function run(generatorFunction) { var generatorItr = generatorFunction(resume); function resume(callbackValue) { generatorItr.next(callbackValue); } generatorItr.next() }
最后執(zhí)行代碼如下:
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"
這樣就完美的避免了回調(diào)地獄噢!
更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript中json操作技巧總結(jié)》、《JavaScript切換特效與技巧總結(jié)》、《JavaScript查找算法技巧總結(jié)》、《JavaScript動(dòng)畫(huà)特效與技巧匯總》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
相關(guān)文章
基于javascript實(shí)現(xiàn)數(shù)字英文驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)數(shù)字英文驗(yàn)證碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01發(fā)個(gè)自己寫(xiě)的表格操作類(添加,刪除,排序,上移,下移)
發(fā)個(gè)自己寫(xiě)的表格操作類(添加,刪除,排序,上移,下移)...2006-11-11ES6知識(shí)點(diǎn)整理之模塊化的應(yīng)用詳解
這篇文章主要介紹了ES6知識(shí)點(diǎn)整理之模塊化的應(yīng)用,結(jié)合實(shí)例形式分析了基于node.js環(huán)境下模擬ES6模塊化配置與使用相關(guān)操作技巧,需要的朋友可以參考下2019-04-04微信小程序自定義純凈模態(tài)框(彈出框)的實(shí)例代碼
這篇文章主要介紹了微信小程序自定義純凈模態(tài)框(彈出框)的實(shí)例代碼,代碼簡(jiǎn)答易懂,非常不錯(cuò),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03Js判斷兩個(gè)數(shù)組是否相等的幾種常見(jiàn)場(chǎng)景
無(wú)論是在開(kāi)發(fā)中還是面試時(shí),在js中判斷變量是否相等,都是一個(gè)比較常見(jiàn)的問(wèn)題,這篇文章主要給大家介紹了關(guān)于Js判斷兩個(gè)數(shù)組是否相等的幾種常見(jiàn)場(chǎng)景,需要的朋友可以參考下2024-07-07js鼠標(biāo)及對(duì)象坐標(biāo)控制屬性詳細(xì)解析
這篇文章主要是對(duì)js鼠標(biāo)及對(duì)象坐標(biāo)控制屬性進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12微信小程序動(dòng)態(tài)顯示項(xiàng)目倒計(jì)時(shí)效果
這篇文章主要為大家詳細(xì)介紹了微信小程序動(dòng)態(tài)顯示項(xiàng)目倒計(jì)時(shí),格式如4天7小時(shí)58分鐘39秒,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06javascript 同時(shí)在IE和FireFox獲取KeyCode的代碼
以前一直在IE8中測(cè)試網(wǎng)站,后來(lái)寫(xiě)的一部分含有Ajax的代碼出現(xiàn)了故障,不得已下載了Firefox以及它的插件Firebug,才發(fā)現(xiàn),F(xiàn)F不支持windows.event事件。于是換了一種思路。2010-02-02