【譯】非同步程式設計:Futures

kinsomy發表於2018-10-07

關鍵點

  • dart是單執行緒語言
  • 同步程式碼會阻塞你的程式
  • 使用Future物件來執行非同步操作
  • 在async函式裡使用await關鍵字來掛起執行知道一個Future操作完成
  • 或者使用then()方法
  • 在async函式裡使用try-catch表示式捕獲錯誤
  • 或者使用catchError()方法
  • 可以使用鏈式操作future物件來按順序執行非同步函式

dart是一個單執行緒的程式語言,如果編寫了任何阻塞執行執行緒的程式碼(例如耗時計算或者I/O),程式就會被阻塞。非同步操作可以讓你在等待一個操作完成的同時完成其他工作。Dart使用Future物件來進行非同步操作。

介紹

先來看一個會導致阻塞的程式程式碼:

// Synchronous code
void printDailyNewsDigest() {
  var newsDigest = gatherNewsReports(); // Can take a while.
  print(newsDigest);
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}
複製程式碼

程式會收集當天的新聞並且列印出來(耗時操作),然後列印一些使用者感興趣的其他資訊。

<gathered news goes here>
Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny.
Baseball score: Red Sox 10, Yankees 0
複製程式碼

這段程式碼是有問題的,當printDailyNewsDigest()方法做耗時操作阻塞,剩下的其他程式碼無論多長時間都只有等到printDailyNewsDigest()返回結果之後才能繼續執行。

為了幫助保持應用程式的響應,Dart庫的作者在定義可能做耗時工作的函式時用了非同步模型。這些函式的返回值是一個future。

future是什麼

一個future是一個Future的泛型Future<T>物件,代表了一個非同步操作產生的T型別的結果。如果結果的值不可用,future的型別會是Future<void>,當返回一個future的函式被呼叫了,將會發生如下兩件事: 1.這個函式加入待完成的佇列並且返回一個未完成的Future物件。 2.接著,當這個操作結束了,Future物件返回一個值或者錯誤。

編寫返回一個future的程式碼時,有兩面兩個可選方法:

  • 使用async和await關鍵字
  • 使用FutureAPI

async 和 await

asyncawait 關鍵字是Dart語言非同步支援的一部分。允許你不使用Future api像編寫同步程式碼一樣編寫非同步程式碼。一個非同步函式的函式體前面要生命async關鍵字,await關鍵字僅在非同步函式裡生效。

下面的程式碼就是一個使用了Future api的例子。

import 'dart:async';

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then(print);
  // You don't *have* to return the future here.
  // But if you don't, callers can't await it.
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}

printWinningLotteryNumbers() {
  print('Winning lotto numbers: [23, 63, 87, 26, 2]');
}

printWeatherForecast() {
  print("Tomorrow's forecast: 70F, sunny.");
}

printBaseballScore() {
  print('Baseball score: Red Sox 10, Yankees 0');
}

const news = '<gathered news goes here>';
const oneSecond = Duration(seconds: 1);

// Imagine that this function is more complex and slow. :)
Future<String> gatherNewsReports() =>
    Future.delayed(oneSecond, () => news);
複製程式碼

main()方法裡面printDailyNewsDigest()是一個被呼叫的,但是因為等待了一秒延遲執行,所以在最後才被列印出來。這個程式的執行順序如下:

【譯】非同步程式設計:Futures

1.程式開始執行

2.main方法呼叫printDailyNewsDigest(),但是不會立即返回,會呼叫gatherNewsReports()

3.gatherNewsReports()開始收集新聞同時返回一個Future

4.printDailyNewsDigest()使用then()指定一個Future的響應結果,呼叫then()返回一個新完成的Future結果作為他的回撥。

5.剩下的print方法是同步的會依次執行。

6.當所有的新聞都被收集到了,帶有新聞資訊的Future被gatherNewsReports()返回。

7.then()方法執行,將future返回的String作為引數傳遞給print列印。

future.then(print)等同於future.then((newsDigest) => print(newsDigest))

所以printDailyNewsDigest()還可以寫成:

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then((newsDigest) {
    print(newsDigest);
    // Do something else...
  });
}
複製程式碼

newsDigest是gatherNewsReports()返回的結果。

即時這個Future的型別是Future<void>,也需要傳遞一個引數給then()的回撥,直接用下劃線_表示。

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then((_) {
    // Code that doesn't use the `_` parameter...
    print('All reports printed.');
  });
}
複製程式碼

處理錯誤

在Future api裡,可以使用catchError()捕獲錯誤:

Future<void> printDailyNewsDigest() =>
    gatherNewsReports().then(print).catchError(handleError);
複製程式碼

如果請求不到新聞並且失敗了,上面的程式碼將會執行:

1.gatherNewsReports()完成返回的future攜帶了error

2.then()方法中的print不會被執行

3.catchError()捕獲到處理錯誤,future被catchError()正常完成並返回,錯誤不會繼續傳遞下去。

更多細節閱讀 Futures and Error Handling

呼叫多個函式返回futures

有三個函式,expensiveA()expensiveB()expensiveC(), 它們都返回Future物件。你可以順序呼叫它們(one by one),或者也可以同時呼叫三個函式並且當所有結果都返回時再做處理。Future api支援以上的操作。

用then()進行鏈式呼叫

按順序使用then()呼叫每個方法

expensiveA()
    .then((aValue) => expensiveB())
    .then((bValue) => expensiveC())
    .then((cValue) => doSomethingWith(cValue));
複製程式碼

這是個巢狀呼叫,上一個函式的返回結果作為下一個函式的引數。

使用Future.wait()等待多個方法一起完成

如果好幾個函式的執行順序無關緊要,可以使用Future.wait()

Future.wait([expensiveA(), expensiveB(), expensiveC()])
    .then((List responses) => chooseBestResponse(responses, moreInfo))
    .catchError(handleError);
複製程式碼

當傳遞一個future列表給Future.wait()時,它會立即返回一個Future,但是直到列表裡的future都完成的時候,這個Future才會完成,他會返回一個列表裡面是每一個future產生的結果資料。

如果任何一個呼叫的函式產生了錯誤,都會被catchError()捕獲到去處理錯誤。

相關資料

相關文章