本文首發於個人部落格
前言
關於非同步,相信很多開發者都是經常用到的。不過,不同的語言有不同的處理方式
一般耗時操作的處理
針對如何處理耗時的操作,不同的語言有不同的處理方式。
-
處理方式一: 多執行緒,比如
Java
、C++
,Objective C
我們普遍的做法是開啟一個新的執行緒(Thread
),在新的執行緒中完成這些非同步的操作,再通過執行緒間通訊的方式,將拿到的資料傳遞給主執行緒。 -
處理方式二: 單執行緒+事件迴圈,比如
JavaScript
、Dart
都是基於單執行緒加事件迴圈來完成耗時操作的處理。
阻塞式呼叫和非阻塞式呼叫
-
阻塞和非阻塞關注的是程式在等待呼叫結果(訊息,返回值)時的狀態。
-
阻塞式呼叫: 呼叫結果返回之前,當前執行緒會被掛起,呼叫執行緒只有在得到呼叫結果之後才會繼續執行。
-
非阻塞式呼叫: 呼叫執行之後,當前執行緒不會停止執行,只需要過一段時間來檢查一下有沒有結果返回即可。
例如你平時做飯的時候,把米放在電飯煲裡面煮,這時候你可以在旁邊坐著等著米飯做好,然後去做菜,這就是阻塞式呼叫。當然了。你也可以煮飯時候,準備菜,啤酒鴨,麻辣小龍蝦。。這就是非阻塞式呼叫。
Dart事件迴圈
什麼是事件迴圈
單執行緒模型中主要就是在維護著一個事件迴圈(Event Loop)。
事件迴圈是什麼呢?
-
事實上事件迴圈並不複雜,它就是將需要處理的一系列事件(包括點選事件、IO事件、網路事件)放在一個事件佇列(Event Queue)中。
-
不斷的從事件佇列(Event Queue)中取出事件,並執行其對應需要執行的程式碼塊,直到事件佇列清空位置。
-
當我們有一些事件時,比如點選事件、IO事件、網路事件時,它們就會被加入到eventLoop中,當發現事件佇列不為空時發現,就會取出事件,並且執行。
具體使用
例子
我們先看一個例子:如下程式碼中,用sleep代替網路請求的耗時操作
import 'dart:io';
main(List<String> args) {
print("start");
String res = getData();
print(res);
print("end");
}
String getData(){
sleep(Duration(seconds: 2));
return "hello world";
}
複製程式碼
輸出結果為:
start
2秒鐘之後輸出
hello world
end
很顯然阻塞了後面的程式碼執行
使用Future
Future
表示一件“將來”會發生的事情,將來可以從Future中取到一個值。
有了Future之後,通過.then的回撥去獲取請求到的結果
import 'dart:io';
main(List<String> args) {
print("start");
var res = getData();
res.then((value) {
print(value);
});
print("end");
}
Future<String> getData(){
return Future<String>(() {
sleep(Duration(seconds: 2));
return "hello world";
});
}
複製程式碼
catchError
如果有異常的話,我們可以用catchError來捕獲
import 'dart:io';
main(List<String> args) {
print("start");
var res = getData();
res.then((value) {
print(value);
}).catchError((error){// 捕獲出現異常時的情況
print(error);
});
print("end");
}
Future<String> getData(){
return Future<String>(() {
sleep(Duration(seconds: 2));
// return "hello world";
throw Exception("請求異常");
});
}
複製程式碼
輸出為
start
end
Exception: 請求異常
Future的鏈式呼叫
import 'dart:io';
main(List<String> args) {
print("start");
var res = getData();
res.then((value) {
print(value);
return "第一次呼叫完成";
}).then((value2){
print(value2);
return "第二次呼叫完成";
}).then((value3){
print(value3);
}).catchError((error){
print(error);
});
print("end");
}
Future<String> getData(){
return Future<String>(() {
sleep(Duration(seconds: 2));
return "hello world";
// throw Exception("請求異常");
});
}
複製程式碼
輸出
start
end
hello world
第一次呼叫完成
第二次呼叫完成
直接獲取一個完成的Future,該Future會直接呼叫then的回撥函式
import 'dart:io';
main(List<String> args) {
print("start");
Future.value("測試").then((value){
print(value);
});
print("end");
}
複製程式碼
輸出為:
start
end
測試
我們可以看到,測試
是最後才列印,這是因為Future中的then會作為新的任務會加入到事件佇列中(Event Queue),加入之後你需要排隊執行。
總結
- 建立一個Future(可能是我們建立的,也可能是呼叫內部API或者第三方API獲取到的一個Future,總之你需要獲取到一個Future例項,Future通常會對一些非同步的操作進行封裝);
- 通過.then(成功回撥函式)的方式來監聽Future內部執行完成時獲取到的結果;
- 通過.catchError(失敗或異常回撥函式)的方式來監聽Future內部執行失敗或者出現異常時的錯誤資訊;
- 可以鏈式呼叫
- 直接獲取一個完成的Future,該Future會直接呼叫then的回撥函式
QQ交流群:592831498