本文目錄一覽:
- 1、如何進行nodejs非同步編程
- 2、如何優雅處理 async await 錯誤——解讀小而美的 awaitjs 庫
- 3、我對JS非同步執行機制的理解
- 4、如何在一個類中實現非同步
- 5、javascript同步和非同步的區別與實現方式
如何進行nodejs非同步編程
我建設玩一下AJAX,是希望您體驗一下非同步,並不是希望您了解AJAX這機制的實現方法,因為AJAX是一個特別典型且簡單的非同步場景,比如:
執行某個函數 – 執行語句A,B,C,D – 在D語句發起非同步請求,同時向引擎註冊一個回調事件 – 執行E,F,G -退出函數塊 ,引擎Loop…Loop…Loop,此時非同步的請求得到了Response,之前註冊的回調被執行。
實際上Node.js主要是為了應對主流web app存在大量I/O等待而CPU閑置的場景所衍生的解決方案,而在架構上,它的後端有一個底層的worker封裝,每當你有一個諸如addUser這樣的I/O操作時,它們都會被交由worker去執行從而達到讓出儘快讓出當前函數的執行權的目的,在向引擎註冊完回調後,內部會通過事件輪詢去檢查該I/O事件的句柄,當句柄顯示該事件操作完成後,則註冊的回調則被執行。
所以,假設有人(按題設,簡化一下場景,有且只有2個人)同時請求addUser(A)和userList(B),B的請求會在執行完A的請求內部所有同步代碼後被執行,而哪怕worker此時仍然在進行addUser 這一 I/O操作,用戶B也並不會被引擎掛起或者等待。這就是為什麼Node.js單節點卻一樣可以擁有高負載能力的原因。
至於什麼樣的代碼是非同步的,你看看node文檔里fs模塊的使用方法就知道了,大概的形式就是如下這種。
module.method( args [,callback] )
當然還有一種比較極端的情況,假設您使用的資料庫是山寨的,驅動是基於同步實現的,那麼B就該等多久等多久把,樹蔭底下喝杯茶,下個棋,和後面的C,D,E,F,G打個招呼唄~~~
我推薦您先去玩一下前端的AJAX了解一下 非同步編程方式,體驗一下非同步的「感覺」,然後看一本關於java的書。
Node.js 是一款基於Event-driven的模型構建的Framework,它典型的特徵就是通過內置的事件輪詢來調度事件,通常來說node.js的資料庫驅動都是基於非同步實現的,所以在實際情況中,A提交博客和B註冊用戶這兩個請求是可以同時由Node.js 來handle,並按照實際操作的處理事件分別調度給予瀏覽器響應。
當然,假設您在業務代碼里寫了一個耗時很久的同步代碼(比如直接寫一個while(true)的loop,Node就死了),由於JavaScript本身單線程的限制,所以整個App就會被block住,後續的事件/程序只有等到該段代碼執行完成之後才會被處理,這也是為什麼我們通常不建議在Node.js層做大規模計算(JS本身的計算效率太低,會導致Node吞吐量會大大降低),而傾向由C++的拓展去實現。
如何優雅處理 async await 錯誤——解讀小而美的 awaitjs 庫
最近有在讀一些比較優秀的npm包的代碼,起因是感覺自己現在寫的代碼還是不夠規範,不夠簡潔。
可是我又不知道到底什麼樣的代碼才算是比較 好 的代碼,在進行一番思考過後我認為還是要站在巨人的肩膀上。
通過閱讀優秀的源碼並從中學習如何寫出讓人覺得賞心悅目的代碼最後再寫文進行章總結對整個學習的過程進行一個梳理同時分享給其他人。
為什麼要在開頭寫這麼多呢?因為我需要為自己堅持下去找一個理由。這樣我才能乘風破浪,一往無前。
話不多說,開始總結。
在正式介紹await-to-js這個庫之前,讓我們先簡單的回顧一下有關於在JavaScript這門語言中,非同步編程的進化之路。在Promise沒出現之前,非同步編程一直是困擾著前端工程師的一個大難題,當時的前輩可能會經常看到下面這種代碼。
這種同時在縱向和橫向延伸的回調中嵌套著回調的代碼又被稱為 回調地獄 。可見這玩意讓人多麼噁心,具體來說有以下這幾個缺點
Promise是一種 優雅 的非同步編程解決方案。從語法上來將,它是一個對象, 代表著一個非同步操作最終完成或失敗,從語意上來講,它是承諾,承諾過一段時間給你一個結果。
由於它的原型存在then,catch,finally會返回一個新的promise所以可以允許我們鏈式調用,解決了傳統的回調地獄的問題。
由於它本身存在all方法,所以可以支持多個並發請求,獲取並發請求中數據。
有了Promise後,上面的代碼可以被寫成下面這樣。
相比較於上面的回調地獄,使用Promise可以幫助我們讓代碼只在縱向發展,並且提供了處理錯誤的回調。顯然優雅了很多。不過就算Promise已經這麼優秀了,可是依然存在兩個每種不足的地方
async 函數是 Generator 函數的語法糖。使用 關鍵字 async 來表示,在函數內部使用 await 來表示非同步。相較於 Generator , async 函數的改進在於下面四點:
此處總結參考自: 理解async/await[1]
有了async/await,上面的代碼可以被改寫成下面這樣
同時我們可以對每一次非同步操作進行錯誤處理
這樣一來上面Promise存在的兩個每種不足的地方是不是就被優化了呢?所以說async/await是JS中非同步編寫的最後解決方案我個人覺得一點問題沒有,但是我不知道你看上面的代碼,每一次非同步操作都要用try/catch進行錯誤處理是不是感覺不夠方便不夠智能呢?
作者是這樣介紹這個庫的
中文翻譯過來就是
這裡做個簡單的對比,之前我們在非同步操作中處理錯誤的方法是這樣的
而用了await-to-js之後,我們可以這樣的處理錯誤
是不是簡潔多了呢?
作者究竟用了什麼黑魔法?
你可能不信,源碼只有僅僅15行。
上面這裡是TS版的源碼,但是考慮到有些同學可能還沒接觸過TS,我著重分析一下下面這版JS版的源碼。
這裡我們先拋開errorExt這個自定義的錯誤文本,核心代碼是這樣的
可以看出,其代碼的邏輯用中文解釋是這樣的
經過上面的分析我們可以認定,世界上沒有什麼黑魔法,沒有你做不到,只有你想不到。
這裡我們再來看函數to的第二個參數errorExt不難發現,這玩意其實就是拿來用戶自定義錯誤信息的,通過 Object.assign 將正常返回的error和用戶自定義和合併到一個對象裡面供用戶自己選擇。
源碼不可怕,可怕的是自己的面對未知的恐懼感。
敢於面對,敢於嘗試,才能更上一層樓。
繼續加油,少年。
[1]
:
[2]
How to write async await without try-catch blocks in Javascript:
我對JS非同步執行機制的理解
說起JS的非同步執行機制,如果百度一下,你首先會發現阮一峰的寫過一篇關於非同步機制的文章( ),等你津津有味又一頭霧水的看完,然後繼續看百度的其他結果,然後會發現,阮一峰的這篇被另一個大牛朴靈給批判了
( )。
由此可見,關於非同步執行機制到底是怎麼回事,因為涉及到瀏覽器底層機制,所以不容易徹底了解清楚,就算是大牛阮一峰,也只是通過英文文獻來了解,而且一知半解。我的這篇文章只是試圖儘可能簡單的描述一下JS的非同步執行機制,坦白說,我現在並不能完全弄懂這個機制,所以也不能完全解釋清這個機制,所以,如果我寫的越嚴謹,就越容易出錯,我只能簡單但是較模糊的描述一下:
JS的運行環境是一個很複雜的環境,它裡面有非常多的複雜的名詞事物,用簡單又不嚴謹的說法來說,運行環境里至少有下面這些事物:
有一個國外的web app,專門用來講解非同步事件的門道 Loupe ,這個更接近真實情況。為什麼我不講解這個?因為更複雜了,我們並不打算研究瀏覽器的底層,不是么?
然後說一下任務隊列里的任務。所有任務可以分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,靠主線程自己就可以執行完成的任務;非同步任務指的是,主線程執行開始之後,需要靠主線程之外的線程才能完成的任務。由主線程決定是否要動用其他線程。以下內容,不再提棧,只說主線程。
現在說重點:
非同步任務的執行機制是:
當主線程遇到一個非同步任務,比如一個ajax請求,當主線程執行到 xhr.send() 的時候,這個send命令是立即執行的, 並不會像一些人想像的,拖到所有同步任務的最後面。 然後主線程向http線程發送指令,要求http線程向伺服器發送請求。這裡強調一下http線程,顯然它不是主線程的一部分,因為它可以並發,如果你有100個ajax請求,每個都需要1秒鐘,是不是http線程要花100秒呢?並不是,它會並發100個請求,總共耗時大約1.01秒就完成了。
主線程向以http線程為代表的幾個線程發送指令之後,主線程就暫時不再管這個ajax任務了,而是去看任務隊列里的下一個任務。
http線程發送了請求之後接收反饋,收到之後,形成一個新的事件(可以叫做「我收到啦!」事件),然後插入到回調函數隊列中,因為回調函數隊列的優先順序很低,所以會排到總隊列的最後面,其結果就是:主線程把同步任務都完成了,才開始執行非同步事件的 回調 。 注意,並不是非同步任務在全體同步任務結束之後才開始,而是非同步任務的回調通常在全體同步任務結束之後才開始!非同步任務跟非同步任務的回調是兩回事!是兩個任務!一個鮮明的例子就是 setTimeout(fn, 1000) ,計時是從主線程遇到 setTimeout() 任務,然後分配給計時器線程,計時器線程開始幹活的時候就開始計時了!只不過要1秒之後 fn 才執行! setTimeout() 和 fn 是兩個任務! setTimeout() 是立即執行, fn 才是1秒之後執行。但是 setTimeout() 的執行,人眼是感受不到的,因為並沒有什麼地方有一個秒錶告訴你 setTimeout() 開始執行了;而fn的執行,人眼能感受到,所以人們會錯誤的以為fn才是非同步任務,其實fn並不是, fn 是個回調任務,往往 fn 是同步任務,比如 fn 可能是 console.log(123) ,這怎麼會是非同步任務。
所以,非同步機制是瀏覽器的兩個或以上常駐線程共同完成的,非同步請求是JS主線程和其他某個線程共同完成的,JS的執行線程發起非同步請求(這時瀏覽器會開一條新的HTTP請求線程來執行請求,這時JS自己的任務已完成,繼續執行線程隊列中剩下的其他任務),然後在未來的某一時刻”任務隊列”線程監視到之前的發起的HTTP請求已完成, “任務隊列”就會把完成事件插入到JS執行隊列的尾部等待JS處理 。
最後專門說說定時觸發(settimeout和setinterval)。
定時觸發是由瀏覽器的定時器線程執行的定時計數, 然後在定時時間到達之後,定時器線程把定時處理函數的執行請求插入到JS回調隊列的尾端。
這個1到底是100毫秒之後彈出,還是1000毫秒(或更多時間)後彈出呢?又或是1100毫秒之後彈出?
答案是,1000毫秒多一點點之後彈出。
原因:瀏覽器會先執行setTimeout,也就是開始計時,然後開始執行sometask,執行了1000毫秒,然後去回調隊列里看回調任務,alert(1);早就恭候了,因為定時100毫秒之後alert(1)就可以執行了。所以,等1000毫秒的任務完成,1就會立即彈出,所以答案是1000毫秒多一點點之後彈出。
所以用這兩個函數的時候,實際的執行時間是大於或等於指定時間的,不保證能準確定時的。
最後強調一下setInterval。比如我希望每100毫秒列印一個1。然後,又有極端情況,就是sometask耗時1000毫秒。你以為sometask結束之後會打出10個1么?並不會,只會打出1個1,因為setInterval第一次讀秒結束之後,回調隊列出現了一個alert(1),根據之前的理論,並不會執行。又過了100毫秒之後,計時器線程會去觀察回調隊列是不是已經有了alert(1),如果有,就不再往回調隊列里加alert(1),目的就是為了避免回調疊加執行。
總之,你需要記住,非同步任務就是主線程在任務隊列里看到了這個任務,看了一眼之後就然後安排別的線程幫忙,然後主線程就忙別的去了,別的線程幫忙完事之後,再在隊列末尾放一個新任務叫「幫忙完畢」,到此非同步任務本身就完事。主任務看到「幫忙完畢」任務之後,就去執行回調,回調執行完,這個非同步任務連同回調就全都完事。然後,如果並沒有回調。。。沒有就沒有唄。
如何在一個類中實現非同步
開個線程池,為每個方法的執行分配一個線程,創建一個hashmap結果集,每個方法執行完,將其存入hashmap中,最後通過判斷hashmap的大小,判斷所有方法線程是否執行完畢,執行完畢則返回該hashmap。
非同步編程其實很常見,特別是在出線Node.js之後,非同步編程更是讓很多開發者受益。那麼回到最初的地方,傳統的前端開發中如何實現非同步編程呢?下面列舉了js實現非同步編程的四種方式。方法一:使用回調函數方法二:事件監聽可以定義一個事件,並為這個事件設定處理函數。這樣只有當這個時間發生的情況下,對應的處理函數才會被執行。方法三:事件的發布/訂閱這個模式在NodeJS以及其他JS框架中都有實現,是一個非常常用的非同步編程方式。
方法四:Promise模式ES6中提供了原生的Promise對象,這個模式最開始只是一個構想,後來由一些框架庫實現。Promise對象代表了未來才會知道結果的事件。Promise的基本思路就是,將需要非同步執行的事件儲存起來,然後根據非同步事件之行後的結果狀態執行下一步的操作。具體的Promise對象的原理和ES6中的使用方法將在下一篇文章中更加深入的進行介紹。
多線程實現。
過程如下
創建一下對象:
robot對象
avi保存對象
行走對象
在robot里使用多線程,2個線程就夠,1個執行avi保存對象,1個執行行走對象。
之所以要創建3個對象,主要是考慮到軟體工程的分而治之的思想。
另外如果你真是要製作機器人的話
可以做2個系統一個是運動控制系統,一個是avi存儲系統,系統間不互聯。這樣互相不會有干擾,而且容易實現,不會讓功能混亂。
javascript同步和非同步的區別與實現方式
javascript語言是單線程機制。所謂單線程就是按次序執行,執行完一個任務再執行下一個。
對於瀏覽器來說,也就是無法在渲染頁面的同時執行代碼。
單線程機制的優點在於實現起來較為簡單,運行環境相對簡單。缺點在於,如果中間有任務需要響應時間過長,經常會導致
頁面載入錯誤或者瀏覽器無響應的狀況。這就是所謂的「同步模式」,程序執行順序與任務排列順序一致。對於瀏覽器來說,
同步模式效率較低,耗時長的任務都應該使用非同步模式;而在伺服器端,非同步模式則是唯一的模式,如果採用同步模式個人認為
伺服器很快就會出現12306在高峰期的表現。。。。
非同步模式的四種方式:
1.回調函數callback
所謂回調函數,就是將函數作為參數傳到需要回調的函數內部再執行。
典型的例子就是發送ajax請求。例如:
$.ajax({
async: false,
cache: false,
dataType: ‘json’,
url: “url”,
success: function(data) {
console.log(‘success’);
},
error: function(data) {
console.log(‘error’);
}
})
當發送ajax請求後,等待回應的過程不會堵塞程序運行,耗時的操作相當於延後執行。
回調函數的優點在於簡單,容易理解,但是可讀性較差,耦合度較高,不易於維護。
2.事件驅動
javascript可以稱之為是基於對象的語言,而基於對象的基本特徵就是事件驅動(Event-Driven)。
事件驅動,指的是由滑鼠和熱鍵的動作引發的一連串的程序操作。
例如,為頁面上的某個
$(‘#btn’).onclick(function(){
console.log(‘click button’);
});
綁定事件相當於在元素上進行監聽,是否執行註冊的事件代碼取決於事件是否發生。
優點在於容易理解,一個元素上可以綁定多個事件,有利於實現模塊化;但是缺點在於稱為事件驅動的模型後,流程不清晰。
3.發布/訂閱
發布訂閱模式(publish-subscribe pattern)又稱為觀察者模式(Observer pattern)。
該模式中,有兩類對象:觀察者和目標對象。目標對象中存在著一份觀察者的列表,當目標對象
的狀態發生改變時,主動通知觀察者,從而建立一種發布/訂閱的關係。
jquery有相關的插件,在這不是重點不細說了。。。。回頭寫個實現貼上來
4.promise模式
promise對象是CommonJS工作組提供的一種規範,用於非同步編程的統一介面。
promise對象通常實現一種then的方法,用來在註冊狀態發生改變時作為對應的回調函數。
promise模式在任何時刻都處於以下三種狀態之一:未完成(unfulfilled)、已完成(resolved)和拒絕(rejected)。以CommonJS
Promise/A
標準為例,promise對象上的then方法負責添加針對已完成和拒絕狀態下的處理函數。then方法會返回另一個promise對象,以便於形成promise管道,這種返回promise對象的方式能夠支持開發人員把非同步操作串聯起來,如then(resolvedHandler,
rejectedHandler); 。resolvedHandler
回調函數在promise對象進入完成狀態時會觸發,並傳遞結果;rejectedHandler函數會在拒絕狀態下調用。
Jquery在1.5的版本中引入了一個新的概念叫Deferred,就是CommonJS promise A標準的一種衍生。可以在jQuery中創建
$.Deferref的對象。同時也對發送ajax請求以及數據類型有了新的修改,參考JQuery API。
除了以上四種,javascript中還可以利用各種函數模擬非同步方式,更有詭異的諸如用同步調用非同步的case
只能用team里同事形容java和javascript的一句話作為結尾:
「寫java像在高速路上開車,寫javascript像在草原上開車」————-以此來形容javascript這種無類型的語言有多自由
but,如果草原上都是坑。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/193951.html