因爲前面的HTTP請求用到了異步操做,很多小夥伴都被這個問題折了下腰,今天總結分享下實戰成果。Dart是一個單線程的語言,遇到有延遲的運算(好比IO操做、延時執行)時,線程中按順序執行的運算就會阻塞,用戶就會感受到卡頓,因而一般用異步處理來解決這個問題。當遇到有須要延遲的運算(async)時,將其放入到延遲運算的隊列(await)中去,把不須要延遲運算的部分先執行掉,最後再來處理延遲運算的部分。html
首先看一個案例:web
//HTTP的get請求返回值爲Future<String>類型,即其返回值將來是一個String類型的值 getData() async { //async關鍵字聲明該函數內部有代碼須要延遲執行 return await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //await關鍵字聲明運算爲延遲執行,而後return運算結果 }
而後咱們調用這個函數,想獲取其結果:json
String data = getData();
在書寫時,在IDE中這個代碼是沒有問題的,可是當咱們運行這段代碼時,就報錯了:segmentfault
爲何呢?由於data
是String類型,而函數getData()
是一個異步操做函數,其返回值是一個await
延遲執行的結果。在Dart中,有await
標記的運算,其結果值都是一個Future
對象,Future
不是String類型,因此就報錯了。後端
那若是這樣的話,咱們就無法獲取到延遲執行的結果了?固然能夠,Dart規定有async
標記的函數,只能由await
來調用,好比這樣:app
String data = await getData();
可是要使用await
,必須在有async
標記的函數中運行,不然這個await
會報錯:異步
因而,咱們要爲這個給data
賦值的語句加一個async
函數的包裝:async
String data; setData() async { data = await getData(); //getData()延遲執行後賦值給data }
上面這種方法通常用於調用封裝好的異步接口,好比getData()
被封裝到了其餘dart文件,經過使用async
函數對其調取使用
再或者,咱們去掉async
函數的包裝,在getData()
中直接完成data
變量的賦值:函數
String data; getData() async { data = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //延遲執行後賦值給data }
這樣,data
就獲取到HTTP請求的數據了。就這樣就完了?是滴,只要記住兩點:oop
await
關鍵字必須在async
函數內部使用async
函數必須使用await
關鍵字
PS:
await
關鍵字真的很形象,等一等的意思,就是說,既然你運行的時候都要等一等,那我調用的時候也等一等吧
前面個講到過,直接return await ...
的時候,實際上返回的是一個延遲計算的Future
對象,這個Future
對象是Dart內置的,有本身的隊列策略,咱們就來聊聊這個Future。
先囉嗦一些關於Dart在線程方面的知識。
Dart是基於單線程模型的語言。在Dart也有本身的進程(或者叫線程)機制,名叫isolate。APP的啓動入口main
函數就是一個isolate。玩家也能夠經過引入import 'dart:isolate'
建立本身的isolate,對多核CPU的特性來講,多個isolate能夠顯著提升運算效率,固然也要適當控制isolate的數量,不該濫用,不然走火入魔自廢武功。有一個很重要的點,Dart中isolate之間沒法直接共享內存,不一樣的isolate之間只能經過isolate API進行通訊,固然本篇的重點在於Future
,不展開講isolate,心急的小夥伴能夠參考官方閱讀理解或者參考大神tain335的人肉翻譯。
Dart線程中有一個消息循環機制(event loop)和兩個隊列(event queue和microtask queue)。
若是在event中插入microtask,當前event執行完畢便可插隊執行microtask。若是沒有microtask,就沒辦法插隊了,也就是說,microtask queue的存在爲Dart提供了給任務隊列插隊的解決方案。
當main
方法執行完畢退出後,event loop就會以FIFO(先進先出)的順序執行microtask,當全部microtask執行完後它會從event queue中取事件並執行。如此反覆,直到兩個隊列都爲空,以下流程圖:
注意:當事件循環正在處理 microtask的時候, event queue會被堵塞。這時候app就沒法進行UI繪製,響應鼠標事件和I/O等事件。胡亂插隊也是有代價的~
雖然你能夠預測任務執行的順序,但你沒法準確的預測到事件循環什麼時候會處理你指望的任務。例如當你建立一個延時1s的任務,但在排在你以前的任務結束前事件循環是不會處理這個延時任務的,也就是或任務執行多是大於1s的。
OK,瞭解以上信息以後,再來回到Future,小夥伴可能已經被繞暈了。
Future就是event,不少Flutter內置的組件好比前幾篇用到的Http(http請求控件)的get
函數、RefreshIndicator(下拉手勢刷新控件)的onRefresh
函數都是event。每個被await
標記的句柄也是一個event,每建立一個Future就會把這個Future扔進event queue中排隊等候安檢~
什麼?那microtask呢?固然不會忘了這個,scheduleMicrotask,用法和Future基本同樣。
前面講到,用async
和await
組合,便可向event queue中插入event實現異步操做,好像Future的存在有些多餘的感受,剛開始我本人也有這樣的疑惑,且往下看。
當定義Flutter函數時,還能夠指定其運行結果返回值的類型,以提升代碼的可讀性:
//定義了返回結果值爲String類型 Future<String> getDatas(String category) async { var request = await _httpClient.getUrl(Uri.parse(url)); var response = await request.close(); return await response.transform(utf8.decoder).join(); } run() async{ int data = await getDatas('keji'); //由於類型不匹配,IDE會報錯 }
Future最主要的功能就是提供了鏈式調用。熟悉ES6語法的小夥伴樂開了花,鏈式調用解決兩大問題:明確代碼執行的依賴關係和實現異常捕獲。WTF?還不明白?且看下面這些案例:
//案例1 funA() async{ ...set an important variable... } funB() async{ await funA(); ...use the important variable... } main() async { funB(); } //若是要想先執行funA再執行funB,必須在funB中await funA(); //funB的代碼與funA耦合,未來若是funA廢掉或者改動,funB中還須要通過修改以適配變動。 //案例2 funA() async{ try{ ...set an important variable... }catch(e){ do sth... }finally{ do sth. else... } } funB() async{ try{ ...use the important variable... }catch(e){ do sth... }finally{ do sth. else... } } main() async { await funA(); await funB(); } //沒有明確體現出設置變量和使用變量之間的依賴關係,其餘開發者難以理解你的代碼邏輯,代碼維護困難 //而且若是爲了防止funA()或者funB()因發生異常致使程序崩潰 //要到funA()或者funB()中分別加入`try`、`catch`、`finally`
爲了解決上面的問題,Future提供了一套很是簡潔的解決方案:
//案例3 funA(){ ...set an important variable... //設置變量 } funB(){ ...use the important variable... //使用變量 } main(){ new Future.then(funA()).then(funB()); // 明確表現出了後者依賴前者設置的變量值 new Future.then(funA()).then((_) {new Future(funB())}); //還能夠這樣用 //鏈式調用,捕獲異常 new Future.then(funA(),onError: (e) { handleError(e); }).then(funB(),onError: (e) { handleError(e); }) }
案例3的玩法是async
和await
沒法企及的,所以掌握Future仍是頗有必要滴。固然了,Future的玩法不只僅侷限於案例3,還有不少有趣的玩法,包括和microtask對象scheduleMicrotask配合使用,我這裏就不一一介紹了,你們參考大神tain335的人肉翻譯或者官網閱讀理解吧。
Dart的isolate中加入了event queue和microtask queue後,有了一點協程的感受,或許這就是Flutter爲啥在性能上敢和原生開發叫板的緣由之一吧。本篇的內容比較抽象,若是仍是有不明白的小夥伴,歡迎留言提問,我儘可能回答,哈哈哈,就醬,歡迎加入到Flutter圈子或flutter 中文社區(官方QQ羣:338252156),羣裏有先後端及全棧各路大神鎮場子,加入進來沒事就寫寫APP掙點外快(這個真的有),順便翻譯翻譯官方英文原稿拉一票粉絲,一舉多得何樂而不爲呢。