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