javascript
JS的异步讲解
前言
JS的異步由來已久,各種異步概念也早早堆在開發(fā)者面前。可現(xiàn)實代碼中,仍然充斥了各種因異步順序處理不當(dāng)?shù)腷ug,或因不好好思考,或因不了解真相。今天,就特來再次好好探索一番JS的異步世界。
?
01 異步的由來--單線程
上世紀(jì)末,互聯(lián)網(wǎng)仍處于極慢速時代,穿梭于客戶端與服務(wù)端的請求,對于時間的耗費是如此的奢侈。而即將面世的LiveScript,便被網(wǎng)景公司考慮同時在瀏覽器和服務(wù)端使用,在瀏覽器端對表單進(jìn)行校驗,從而提高表單提交效率。為了將這一腳本語言推向市場,網(wǎng)景與sun聯(lián)合開發(fā),最終以Java冠名為JavaScript。
剛面世的JavaScript,是為網(wǎng)頁設(shè)計人員準(zhǔn)備的,不需要太復(fù)雜的語言設(shè)計,能簡單上手,自然就是最好的。
于是,單線程,弱類型,一開始就成為了JavaScript的基因。而其中的單線程,便是最戲劇性的存在,Ryan Dahl因為JavaScript是單線程語言,從而選擇了js開發(fā)了輕量級服務(wù)器(nodejs),使得js從瀏覽器端延伸到服務(wù)器。隨著JS開發(fā)隊伍和程序復(fù)雜度的同步發(fā)展,異步處理成為了JS程序的重中之重。
?
02 JS是一個充滿異步的世界
先來導(dǎo)入幾個異步的常見場景
dom用戶輸入響應(yīng)
ducument.addEventListener('click', function(){})
Ajax
$.ajax(<url>, function() {})
定時/延時
setTimeout(function() {}, 1000) setInterval(function() {}, 1000)
文件讀取
var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function() {}
以上的場景基本有個共同特性,耗時!
舉個栗子,我們?nèi)ャy行取錢,當(dāng)人很多時,如果還是排隊模式,會耗費很多時間(同步模式)。于是設(shè)立了取號機,取了號,不用排隊,在一旁坐著,安心打開電腦寫個文檔,等叫號后再去辦業(yè)務(wù)(異步模式)。
同理,由于單線程的特性,當(dāng)JS應(yīng)用越來越復(fù)雜,耗時的程序如果以同步來進(jìn)行,就會阻塞js的單線程,如大水沖過狹窄的河道,勢必決堤。那JS是怎么開拓導(dǎo)流渠道的呢?其實在js的單線程(主線程)背后,規(guī)律的運行了很多線程:
這些線程就充當(dāng)了JS大江的小河道,當(dāng)短時有大流量時,接納吸收,將過濾處理后的正常水流,再匯入JS主干道。
與其說JS是單線程,不如說JS是有著自動化多線程處理的主線程。無需手動編碼介入新開線程,切換線程,消息同步等冗繁的處理。專用線程會接管相關(guān)任務(wù),并將處理結(jié)果送回主線程進(jìn)行順序處理。
說到這里稍微提一下web worker,雖然是自定義的多線程,最終還是子線程地位,仍舊將處理完成的結(jié)果以回調(diào)函數(shù)方式匯入到主線程進(jìn)行異步處理。
?
03 異步處理一般流程
先看以下代碼,異步模式開始了
var img = new Image() var imgLoadCallback = function() {} img.src = 'http://????' img.onload = callback?
“http君,麻煩幫取一個圖片數(shù)據(jù),好了后交給imgLoadCallback君。” — js主線程老大
“任務(wù)收到,您先忙,圖片請求交給我了,好了之后我叫imgLoadCallback君到休息室排隊,您空了通知下 Event Loop巡檢官。” — http請求線程
?
“圖片已取到,imgLoadCallback君去休息室排隊等候吧!” — http請求線程
imgLoadCallback入棧JS任務(wù)隊列?
“剛好忙完手上的事情了,Event Loop君,幫看下休息室有沒有人排隊” — JS主線程老大
“老大,已把等候者imgLoadCallback叫過來處理任務(wù)” — Event Loop巡檢官
?
“事情都交給合適的人去辦了,突然就清閑下來了,老大就是要這樣當(dāng)啊,嘿嘿嘿… Event Loop君,定時看下休息室有沒有人排隊吧… ” — JS主線程老大
JS主線程通過Event Loop讀取任務(wù)隊列?
講完故事,再來看這張異步示意圖,是否能理解了?
?
image
?
04 回調(diào)處理工具的進(jìn)化
從前面的篇章已經(jīng)能看出來了,異步處理的結(jié)果是通過回調(diào)放置到任務(wù)隊列轉(zhuǎn)接到主線程中的。
北京猿人刀跟火種,這么寫異步回調(diào),看上去也能令人接受。
$.ajax(url: '自家香蕉樹林',data: {picker: '猴子A'},success: function(data) {$.ajax(url: '隔壁老孫家桃林',data: {exchanges: data.香蕉,buyer: '猴子A'},success: function(data) {console.log('向本猴王進(jìn)貢', data.桃子)})} )?
進(jìn)化成人類后交易過程變的復(fù)雜了,于是就變成回調(diào)地獄,傳說中的callback hell
$.ajax(url: '自家香蕉樹林',data: {picker: '老王'},success: function(data) {$.ajax(url: '集市販賣',data: {goods: data.香蕉,seller: '老王'},success: function(data) {$.ajax(url: '隔壁老李桃子鋪',data: {exchanges: data.錢,buyer: '老王'},success: function(data) {console.log('向本王進(jìn)貢', data.桃子)})})} )?
于是發(fā)明了鐵器promise,解決回調(diào)地獄之痛
$.ajax(url: '自家香蕉樹林',data: {picker: '老王'} ) .then(function(data) {return $.ajax(url: '集市販賣',data: {goods: data.香蕉,seller: '老王'}) }) .then(function(data) {return $.ajax(url: '隔壁老李桃子鋪',data: {exchanges: data.錢,buyer: '老王'}) }) .then(function(data) {console.log('向本王進(jìn)貢', data.桃子) })?
關(guān)于promise的升級版async、await,本篇不多說了,理念上基本一致。
繼續(xù)...
這下一次命令,只會來供給本王一次桃子,每次都要發(fā)令,好麻煩,得下個令讓老王每天去賣香蕉買桃子,給我月供100個,于是就發(fā)生了以下的故事
var contributeTime; setInterval(function(){$.ajax(url: '自家香蕉樹林',data: {picker: '老王'}).then(function(data) {return $.ajax(url: '集市販賣',data: {goods: data.香蕉,seller: '老王'})}).then(function(data) {return $.ajax(url: '隔壁老李桃子鋪',data: {exchanges: data.錢,buyer: '老王'})}).then(function(data) {var currentTime = new Date().getTime();if (!contributeTime || (currentTime - contributeTime > '月')) {console.log('向本王進(jìn)貢', [data.桃子,…]); //length=100currentTime = contributeTime;}}) }, '天')?
這過程,好像也太不優(yōu)雅了點。
ReactX的JS版,RxJs來了,將異步看作為單點,將其擴展了時間線,作為流來處理。所以對于一次又一次的進(jìn)貢,都可進(jìn)行時序管理,于是整個過程變成這樣:
import { ajax } from 'rxjs/ajax'; //此處特別寫引入,目的為不與jquery.ajax混淆 import { interval } from 'rxjs'; const ob = interval('天'); const peachPay = ob.pipe(switchMap(x => ajax.post('自家香蕉樹林', {picker: '老王'}))).pipe(switchMap(data => ajax.post('集市販賣', {seller: '老王', goods: data.香蕉}))).pipe(switchMap(data => ajax.post('隔壁老李桃子鋪', {buyer: '老王', exchanges: data.錢}))).pipe(throttle(data => interval('月'))).subscribe(data => console.log(`每月收到月供:${data.桃子.length}個${data.桃子}`));整個過程順著管道不斷變換處理,就是一條全自動流水線!然鵝,然鵝,并一定每月就能供出100個桃子啊,萬一遇到農(nóng)災(zāi),或者經(jīng)濟蕭條…
以上例子僅提供思路,且讀且珍重!
?
05 比工具更重要的,是理解
前端開發(fā)中,諸多剪不斷理還亂的偶現(xiàn)bug來源于異步處理的順序混亂。即便是異步處理工具越來越先進(jìn),由于代碼層面的順序和真實執(zhí)行順序的不一致,也還是容易一不小心犯錯誤。
異步處理工具不是萬能的,還是需不斷將異步原理內(nèi)化入思維模式中,種碼的時候,就需清晰的知道該段代碼會什么時候結(jié)出果實。
?
?
總結(jié)
- 上一篇: .ASP NET Core中缓存问题案例
- 下一篇: JavaScript正则表达式语法与示例