摘要:回調函數(shù)這是最原始的一種異步解決方法。從的對象演化而來對象是提出的一種對異步編程的解決方案,但它不是新的語法,而是一種新的寫法,允許將回調函數(shù)的嵌套改成鏈式調用。
一、前言
異步編程對JavaScript來說非常重要,因為JavaScript的語言環(huán)境是單線程的,如果沒有異步編程將變得非??膳拢烙嫺緹o法使用。這篇文章就來總結一下從最原始的回調函數(shù)到現(xiàn)在的ES6、ES7的新方法。
文章并不會具體介紹每種方法的原理,如果不是特別懂需要詳細了解的同學可以看阮一峰的ES6入門。阮大大介紹得非常具體,從原理到用法。
- 什么是單線程?單線程就是指進程中只有一個線程。單線程執(zhí)行程序時,按照代碼的順序,上一個任務完成后才會執(zhí)行下一個任務。同一個時間只做一件事情。
- 為什么JavaScript是單線程的?JavaScript的主要作用就是操作DOM,如果兩段JS同時操作一個DOM,會引起渲染的沖突。所以JavaScript只能是單線程的。
HTML5中提出的Web Worker,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。
同步是指任務一件一件的按順序完成,上一件沒有完成就無法做下一件;而異步則是指,開始做一件事之后就放在那兒等待結果,不需要守著,繼續(xù)做下一件事即可。
異步可以解決JavaScript單線程效率低、同一事件只能做一件事情的問題。
console.log(1); setTimeOut(()=>{ console.log(2); },1000) console.log(3);
這段代碼首先會打印1,然后打印3,過1000ms以后打印2。
然而這段代碼內部是如何運行的呢,打印1和打印3的命令是同步命令,所以直接按順序放到主進程中執(zhí)行,setTimeOut里的是一個異步命令,在1000ms以后會被放入異步隊列中。而主進程會通過事件循環(huán)(event loop)不斷地從異步隊列中取出命令,然后執(zhí)行。當主進程在1000ms以后查詢到了打印2的命令時,便把這個函數(shù)拿到主進程中執(zhí)行。
二、異步編程的解決辦法這里全部用ajax連續(xù)調用例子,接口是豆瓣的真實接口,可以得到具體的數(shù)據(jù),但有限制每小時150次。
1.回調函數(shù)這是最原始的一種異步解決方法?;卣{函數(shù),就是指一件事做完以后,拿到結果后要做的事情。
var urlBase = "https://api.douban.com/"; var start = 0,count = 5; $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success: function(data){ console.log(data); start+=count; $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ console.log(data); start+=count; $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ console.log(data); } }) } }) } })
這是用jquery的ajax方法調用的豆瓣某個人的收藏的圖書,start和count是豆瓣提供的接口參數(shù),start代表從哪一條數(shù)據(jù)開始獲取,count代表一共獲取多少條數(shù)據(jù)。
從上面的代碼可以看到多個回調函數(shù)的嵌套,如果需要調用得越多,回調也堆積得越多,多了以后代碼就很難維護,時間久了自己也要花很久才能看懂代碼。
改進辦法將每一次回調的方法封裝成函數(shù),代碼量會減少很多。
var urlBase = "https://api.douban.com/"; var start = 0,count = 5; function ajax(start,count,cb){ $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ console.log(data); start+=count; cb && cb(start); } }) } ajax(start,count,function(start){ ajax(start,count,function(start){ ajax(start,count) }) });
但是這樣依然沒有解決“回調地獄”的問題,當每次回調的邏輯操作變得越來越多的時候,代碼依然難以維護。
2.Promise(從jQuery的deferred對象演化而來)Promise對象是ES6提出的一種對異步編程的解決方案,但它不是新的語法,而是一種新的寫法,允許將回調函數(shù)的嵌套改成鏈式調用。
雖然說Promise是ES6提出的標準,但其實jQuery在1.5版本以后就提出了類似的東西,叫做deferred對象。具體學習可以看jQuery的deferred對象詳解。
const urlBase = "https://api.douban.com/"; let start = 0,count = 5; function ajax(start,count){ let dtd = $.Deferred(); $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ start+=count; dtd.resolve(data,start); }, error:function(err){ dtd.reject(err); } }) return dtd; } ajax(start,count).then((data1,start) => { console.log(data1); return ajax(start,count); }).then((data2,start) => { console.log(data2); return ajax(start,count); }).then((data3,start) => { console.log(data3); }).catch((err) => { console.log("這里出錯啦"); })
從這段代碼可以看出來,寫法和promise非常相似了,可以猜測promise就是從deferred演化而來的。
同樣的功能實現(xiàn)可以改成以下寫法:
const urlBase = "https://api.douban.com/"; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data,start); }, error:function(err){ reject(err); } }) }) } ajax(start,count).then((data1,start) => { console.log(data1); return ajax(start,count); }).then((data2,start) => { console.log(data2); return ajax(start,count); }).then((data3,start) => { console.log(data3); }).catch((err) => { console.log("這里出錯啦"); })
Promise使用.then方法解決了回調的問題,但代碼依然冗余,且語義不強,放眼望去全是.then方法,很難找出需要修改的地方。
3.GeneratorGenerator函數(shù)也是ES6中提出的異步編程解決方法,整個 Generator 函數(shù)就是一個封裝的異步任務,或者說是異步任務的容器。最大特點就是可以交出函數(shù)的執(zhí)行權(即暫停執(zhí)行)。
異步操作需要暫停的地方,都用yield語句注明。
const urlBase = "https://api.douban.com/"; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data); }, error:function(err){ reject(err); } }) }) } let gen = function*(){ yield ajax(start,count); start+=count; yield ajax(start,count); start+=count; yield ajax(start,count); } let g = gen(); g.next().value.then((data1) => { console.log(data1); g.next().value.then((data2) => { console.log(data2); g.next().value.then((data3) => { console.log(data3); }) }) })
這樣在gen函數(shù)內三個ajax請求就看起來非常像同步的寫法了,但是執(zhí)行的過程并不清晰,且需要手動.next來執(zhí)行下一個操作。這并不是我們想要的完美異步方案。
4.asyncasync函數(shù)是ES7提出的一種異步解決方案,它與generator并無大的不同,而且可以說它就是generator的一種語法糖。它的語法只是把generator函數(shù)里的*換成了async,yield換成了await,但它同時有幾個優(yōu)點。
(1)內置執(zhí)行器。這表示它不需要不停的next來使程序繼續(xù)向下進行。
(2)更好的語義。async代表異步,await代表等待。
(3)更廣的適用性。await命令后面可以跟Promise對象,也可以是原始類型的值。
(4)返回的是Promise。
const urlBase = "https://api.douban.com/"; let start = 0,count = 5; function ajax(start,count){ return new Promise(function(resolve,reject){ $.ajax({ url: urlBase+"v2/book/user/1219073/collections", type: "GET", dataType: "jsonp", data:{ start:start, count:count }, success:function(data){ start+=count; resolve(data); }, error:function(err){ reject(err); } }) }) } async function getData(){ let data = null; try{ for(let i = 0;i < 3;i++){ data = await ajax(start,count); console.log(data); start+=count; } } catch(err){ console.log(err); } } getData();
用async函數(shù)改寫之后語義清晰,代碼量也減少了,并且內部自帶執(zhí)行器,感覺很符合想象中的異步解決方法。
三、結語到此就把幾種常見的異步回調方法介紹完了,我個人感覺用async+promise是最好的辦法。當然為了更加深刻的理解這些異步解決辦法,一定要多多的用到項目中,多用才會多理解。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/93361.html
摘要:在誕生以前,異步編程的方式大概有下面四種回調函數(shù)事件監(jiān)聽發(fā)布訂閱對象將異步編程帶入了一個全新的階段,中的函數(shù)更是給出了異步編程的終極解決方案。這意味著,出錯的代碼與處理錯誤的代碼,實現(xiàn)了時間和空間上的分離,這對于異步編程無疑是很重要的。 寫在前面 有一個有趣的問題: 為什么Node.js約定回調函數(shù)的第一個參數(shù)必須是錯誤對象err(如果沒有錯誤,該參數(shù)就是null)? 原因是執(zhí)行回調函...
摘要:的翻譯文檔由的維護很多人說,阮老師已經(jīng)有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:更好的異步編程上面的方法可以適用于那些比較簡單的異步工作流程。小結的組合目前是最強大,也是最優(yōu)雅的異步流程管理編程方式。 訪問原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風格,方便你把異步實現(xiàn)的那些細節(jié)藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風格。 換句話說,通過將我們generato...
摘要:對于而言,異步編程我們可以采用回調函數(shù),事件監(jiān)聽,發(fā)布訂閱等方案,在之后,又新添了,,的方案??偨Y本文闡述了從回調函數(shù)到的演變歷史。參考文檔深入掌握異步編程系列理解的 對于JS而言,異步編程我們可以采用回調函數(shù),事件監(jiān)聽,發(fā)布訂閱等方案,在ES6之后,又新添了Promise,Genertor,Async/Await的方案。本文將闡述從回調函數(shù)到Async/Await的演變歷史,以及它們...
摘要:參考文章珠峰架構課墻裂推薦細說異步函數(shù)發(fā)展歷程異步編程謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發(fā),請不要吝嗇你的贊和,您的肯定是我前進的最大動力。 知其然知其所以然,首先了解三個概念: 1.什么是同步? 所謂同步,就是在發(fā)出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由調用者主動等待這個調用的結果。此調...
閱讀 734·2023-04-25 20:32
閱讀 2306·2021-11-24 10:27
閱讀 4540·2021-09-29 09:47
閱讀 2257·2021-09-28 09:36
閱讀 3660·2021-09-22 15:27
閱讀 2778·2019-08-30 15:54
閱讀 385·2019-08-30 11:06
閱讀 1282·2019-08-30 10:58