逐步加深的异步操作(上)
? ? ?最近呢?第一主要是加班比較嚴重,還被高中同桌嘲諷:屌絲程序員,還諷刺我頭發掉的快等等;第二呢 我最近和大學室友準備開一個開源項目,最近正在瘋狂的籌劃中,你能想象 設計:自己人、后臺:自己人、前端:自己人、ui沒有的痛苦嗎?但是我們還是想做一個出來。哈哈,反正我們這種屌絲的想法不要錢。??? ? ?好了,不嗶嗶了,這篇文章主要是來研究異步操作的。如果做過Android端的都知道,Android端有rxjava、多線程、aysnctask等好多東西來處理同步和異步的問題,但是初涉前端,很多東西都是朦朧的,我就由淺及深談一談我所理解的異步操作。
##說在前頭: 同步和異步呢?是程序員難以理解的一個東西,其實我對同步、異步也是略窺門徑。我就簡單說說我了解的同步、異步。 說起同步、我在這里把同步異步來做一個小例子幫助大家理解: 背景:小明暗戀著小紅 同步:就好比:小明約小紅去吃飯,小明必須得到小紅的響應才能做下一步動作,不然小明會在處于等待狀態。 異步:就好比:小紅約小明去吃飯。小紅對著空氣喊了一聲“小明,我去吃飯了”,然后小紅就揚長而去了,不需要得到小明的回應。 總的來說,同步異步就只需要知道四個概念:
- 同步 所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。按照這個定義,其實絕大多數函數都是同步調用(例如sin, isdigit等)。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。=
- 異步 異步的概念和同步相對。當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。=
- 阻塞 阻塞調用是指調用結果返回之前,當前線程會被掛起。函數只有在得到結果之后才會返回。有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。對于同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。
- 非阻塞 非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。
一、黃階下級斗技:胡搞一通,能運行就好了
我們剛開始入門的時候,經常會用一種完成任務的心態去實現功能。就例如現在頁面啟動的時候有N個請求,這就很有可能會寫成這個樣子:
{function get1(){console.log("get1...")setTimeout(function(){post1()},2000)}function get2(){console.log("get2...")setTimeout(function(){post2()},2000)}function post1(){console.log("post1...")setTimeout(function(){get2()},2000)}function post2(){console.log("post2...")setTimeout(function(){console.log("render...")},2000)}get1() } 復制代碼運行結果如下
當然,還有更恐怖堆積在一個函數里面。這樣的寫法的好處就是所有的東西都處于一根線上面,便于大家理解。但是我們設身處地的想一下:如果你是用戶,打開網站到看到視圖需要這么長的白屏時間,這種體驗就大打折扣。此時就需要異步操作了。 看下面一段代碼 {function get1(){console.log("get1...")setTimeout(function(){console.log("get1 end...")},1000)}function get2(){console.log("get2...")setTimeout(function(){console.log("get2 end...")},1000)}function post1(){console.log("post1...")setTimeout(function(){console.log("post1 end...")},1000)}function post2(){console.log("post2...")setTimeout(function(){console.log("post2 end and render...")},1000)}get1()get2()post1()post2() } 復制代碼運行結果如下:
如果按照我們通常的想法應該是get1 end之后才開始get2 end。但是上面的結果卻大相徑庭。查閱的一些官方文檔,我也差不多略窺門徑了,首先在js中存在一個執行隊列和一個異步隊列,如果存在有耗時任務,就會將該任務直接丟到異步隊列,然后繼續運行執行隊形隊列的任務。這就是js自己實現的一個異步操作。那既然js內部自己就已經開始在使用異步了,我們為什么不好好來研究一波異步操作呢?
##二、 黃階上級斗技:回調 如果你和我一樣,都有一點java/android的底子,就一定會對回調有某種執念,同樣js內部也可以完成這種寫法,具體代碼如下:
{function get1(...callback){console.log("get1...")setTimeout(function(){console.log("get1 end...")for(let index of callback){index()}},1000)console.log("do something...")}function get2(){console.log("get2...")setTimeout(function(){console.log("get2 end...")},1000)}function post1(){console.log("post1...")setTimeout(function(){console.log("post1 end...")},1000)}function post2(){console.log("post2...")setTimeout(function(){console.log("post2 end and render...")},1000)}get1(get2,post1,post2) } 復制代碼運行結果如下:
這種寫法看了大概就能明白回調的用法了。但是回調是什么? 百科是這樣解釋的:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數?;卣{函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。 在js中,我看到一種解釋是這樣說的:函數A作為參數(函數引用)傳遞到另一個函數B中,并且這個函數B執行函數A。我們就說函數A叫做回調函數。如果沒有名稱(函數表達式),就叫做匿名回調函數。當然回調不一定只用于異步,一般同步(阻塞)的場景下也經常用到回調,比如要求執行某些操作后執行回調函數。
{function f1(callback){console.log("do something=======>");// do somethings(callback && typeof(callback) === "function")&& callback();}function f2(){console.log("do others things======>");}f1(f2) } 復制代碼關于回調呢?就算這么多,具體的思想前面也說了,es5以前回調是處理異步的主要方式。當然現在都2018年,es也出了很多的新方法,我來首先介紹一個promise。 ##三、 玄階中級斗技:promise 說起promise這個東西,其實我在剛開始接觸到前端的時候就有用到,主要是用來異步操作網絡請求。首先介紹的是promise的兩個特點:
(1)對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。
pomise內存實現了兩個方法:resolve、reject。resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。
{//模擬一個網絡請求let promise = new Promise((res,rej)=>{console.log("promise start====>")let data = Math.random()*10setTimeout(()=>{if(data>2){res("promise end sussess!!")}else{rej("promise end fail!!")}},3000)})promise.then(res=>{console.log(res)},res=>{console.log(res)}) } 復制代碼效果圖如下:
由上面的效果我們可以明明白白的看出來promise新建之后就會立即執行。好的,我們這里開始解釋前面的說法。promise在new出來之后就開始執行,里面會收到兩個回調--resolve、reject。我們這里把知道觸發這兩個回調的狀態叫做pending,一旦觸發兩個回調之后就會立刻就會凝固。 {//模擬一個網絡請求let promise = new Promise((res,rej)=>{console.log("promise start====>");throw new Error("test error")})promise.then(res=>{console.log(res)},res=>{console.log(res.message)}) } 復制代碼 現在我們在模擬網絡請求中出現錯誤的情況,如上可知reject本身就是可以攔截錯誤的。如果在promise中出錯之后會走reject回調。但是我在看很多人的源碼,很多大佬是這么寫的: {let promise = new Promise((res,rej)=>{console.log("promise start====>");throw new Error("test error")res("promise end sussess!!")})promise.then(res=>{console.log(res)}).catch(res=>{console.log(res.message)}) } 復制代碼剛開始我沒有想通,但是當我寫了很多東西之后就開始逐漸明白了。如下面的例子
{let promise = new Promise((res,rej)=>{console.log("promise start====>");res("promise end sussess!!")})promise.then(res=>{console.log(res)throw new Error("i meet some bug = =.");},res=>{console.log(res)}) } 復制代碼效果圖如下:
我們發現reject并不能捕獲then里面的錯誤,此時就用到大神們推薦的方式了。 {//模擬一個網絡請求let promise = new Promise((res,rej)=>{console.log("promise start====>");res("promise end sussess!!")})promise.then(res=>{console.log(res)throw new Error("i meet some bug = =.");}).catch(res=>{console.log(res.message)}) } 復制代碼效果圖如下:
并且我了解到源碼中看到catch的實現方式就是: then(null, rejection) <===> catch 復制代碼我從進一步了解源碼到:無論是resolve,和是reject都會返回一個promise,所以現在的代碼就可以這么來寫。
{ {let promise = new Promise((res,rej)=>{console.log("promise start====>");res("promise end sussess!!")})promise.then(res=>{console.log(res)}).then(res=>{console.log(res)return "next next promise!!"}).then(res=>{console.log(res);throw new Error("i meet some bug = =.");}).catch(res=>{console.log(res.message)}).then(res=>{throw new Error("ai ya = =.");}) } 復制代碼效果圖如下:
這里值得注意的兩點:- 如果沒有return下面的then是會返回undefined,相當于系統默認reject(undefined)
- catch 并不能捕獲到之后的error,如果想捕捉,就得繼續去捕獲了。
說到這里了,我就再插一句:我在最新的es2018中看到了finally方法,類比于java中的try,catch, finally,如果有興趣,可以去看看最新的es2018的規則。我在這里就不做贅述了。我在這里最后來介紹promise在我的開發途徑中常用的幾個方法:
- Promise.all() 使用方法:
效果圖:
上面值得注意的是:執行順序是按照all方面里面的數組順序執行的,這就能為我們封裝網絡請求做一定的便利。 {let p1 = new Promise((res,rej)=>{res('success!!');});let p2 = Promise.resolve("dosomething");let p3 = Promise.reject("i have bug ...")Promise.all([p1,p2,p3]).then(res=>{console.log(res);}).catch(res=>{console.log(res)})} 復制代碼如果all之中存在拋出錯誤,其效果圖如下
如果存在錯誤直接走catch方法. 我在這里做一下總結: ** Promise.all可以將多個Promise實例包裝成一個新的Promise實例。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果數組,而失敗的時候則返回最先被reject失敗狀態的值。**- Promise.race() 使用方法:
效果圖如下:
race相較于all方法就是,里面promise誰先處于完成狀態就會走哪個方法。說在最后
我看了時間點又看了看我的計劃,看來我的焚決今晚是寫不完了。先就這樣吧。因為考慮到Generator這個到使用都花了近三天的時間,我來講這個不知道一個小節能不能講的通,更何況還有decorator這種bug呢?。寫文章第一:是為了梳理自己的知識體系;第二:寫給讀者。如果我寫的東西,讓一個新人都能看懂,這就說明我也達到了梳理知識體系的效果,所以我決定將異步操作分成幾塊來講解,爭取能讓所有看文章的人都明白我寫的東西。
12點了,不寫了 不寫了,好了,洗澡去了...最后說一句??? 小米漲了 ahhhhhhhhhh
轉載于:https://juejin.im/post/5cdd4767e51d45475d5e8e0a
總結
以上是生活随笔為你收集整理的逐步加深的异步操作(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Java并行任务框架Fork/Join
- 下一篇: 2019山东省赛B - Flipping
