1. 程式人生 > >Dart非同步程式設計之Future

Dart非同步程式設計之Future

Dart非同步程式設計包含兩部分:Future和Stream
本文將詳細介紹Future

Dart非同步程式設計-future

非同步程式設計:Futures

Dart是一個單執行緒程式語言。如果任何程式碼阻塞執行緒執行都會導致程式卡死。非同步程式設計防止出現阻塞操作。Dart使用Future物件表示非同步操作。

介紹

如下程式碼可能導致程式卡死

// Synchronous code
printDailyNewsDigest() {
  String news = gatherNewsReports(); // Can take a while.
  print(news);
}

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結束才能繼續執行。當用戶想知道自己是否中彩票,明天的天氣和誰贏得比賽,都必須等待printDailyNewsDigest讀取結束。

為了程式及時響應,Dart的作者使用非同步程式設計模型處理可能耗時的函式。這個函式返回一個Future

什麼是Future

Future表示在將來某時獲取一個值的方式。當一個返回Future的函式被呼叫的時候,做了兩件事情:

  1. 函式把自己放入佇列和返回一個未完成的Future物件
  2. 之後當值可用時,Future帶著值變成完成狀態。
    為了獲得Future的值,有兩種方式:
  3. 使用async和await
  4. 使用Future的介面

Async和await

async 和 await 是Dart async和await關鍵字是Dart非同步支援的一部分。他們允許你像寫同步程式碼一樣寫非同步程式碼和不需要使用Future介面。

注:在Dart2中有輕微的改動。async函式執行時,不是立即掛起,而是要執行到函式內部的第一個await。
在多數情況下,我們不會注意到這一改動

下面的程式碼使用Async和await讀取新聞

// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

printDailyNewsDigest() async {
  String news = await gatherNewsReports();
  print(news);
}

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>';
Duration oneSecond = const Duration(seconds: 1);

final newsStream = new Stream.periodic(oneSecond, (_) => news);

// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;

// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future gatherNewsReportsFromServer() => HttpRequest.getString(
//      'https://www.dartlang.org/f/dailyNewsDigest.txt',
//    );

執行結果:

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

從執行結果我們可以注意到printDailyNewsDigest是第一個呼叫的,但是新聞是最後才打印,即使只要一行內容。這是因為程式碼讀取和列印內容是非同步執行的。

在這個例子中間,printDailyNewsDigest呼叫的gatherNewsReports不是阻塞的。gatherNewsReports把自己放入佇列,不會暫停程式碼的執行。程式列印中獎號碼,天氣預報和比賽分數;當gatherNewsReports完成收集新聞過後列印。gatherNewsReports需要消耗一定時間的執行完成,而不會影響功能:使用者在讀取新聞之前獲得其他訊息。

下面的圖展示程式碼的執行流程。每一個數字對應著相應的步驟
async-await

  1. 程式開始執行。

  2. 主函式呼叫asynchronousCode()函式。

  3. asynchronousCode()函式呼叫printDailyNewsDigest_async()函式,然後函式立即返回一個Future 物件。

  4. asynchronousCode()函式其它中接下來的程式碼依次開始執行。以此執行printWinningLotteryNumbers(),printWeatherForecast(),printBaseballScore()函式,並返回對應的結果。

  5. printDailyNewsDigest_async()函式體開始執行。

  6. 當執行到await 語句時:程式暫停,等待gatherNewsReports_async()函式執行結果。

  7. 一旦gatherNewsReports_async()函式的Future 執行完成,printDailyNewsDigest_async()函式將繼續執行。

  8. 當printDailyNewsDigest_async()函式執行完成後,程式結束。

    注:如果async函式沒有明確指定返回值,返回的null值的Future

錯誤處理

如果在Future返回函式發生錯誤,你可能想捕獲錯誤。Async函式可以用try-catch捕獲錯誤。

printDailyNewsDigest() async {
  try {
    String news = await gatherNewsReports();
    print(news);
  } catch (e) {
    // Handle error...
  }
}

try-catch在同步程式碼和非同步程式碼的表現是一樣的:如果在try塊中間發生異常,那麼在catch中的程式碼執行

連續執行

你可以使用多個await表示式,保證一個await執行完成過後再執行下一個

// Sequential processing using async and await.
main() async {
  await expensiveA();
  await expensiveB();
  doSomethingWith(await expensiveC());
}

expensiveB函式將等待expensiveA完成之後再執行。

Future API

在Dart1.9之前還沒有新增async和await時,你必須使用Future介面。你可能在一些老的程式碼中間看到使用Future介面

為了使用Future介面寫非同步程式碼,你需要使用then()方法註冊一個回撥。當Future完成時這個回撥被呼叫。

下面的程式碼使用Future介面:

// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

printDailyNewsDigest() {
  final future = gatherNewsReports();
  future.then((news) => print(news));
}

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>';
Duration oneSecond = const Duration(seconds: 1);

final newsStream = new Stream.periodic(oneSecond, (_) => news);

// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;

// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future gatherNewsReportsFromServer() => HttpRequest.getString(
//      'https://www.dartlang.org/f/dailyNewsDigest.txt',
//    );

注意到printDailyNewsDigest被首先呼叫,但是新聞在最後被列印。這是因為程式碼讀取內容是非同步執行的。

程式執行流程:

  1. 程式開始執行
  2. main函式呼叫printDailyNewsDigest函式,在函式內部呼叫gatherNewsReports
  3. gatherNewsReports收集新聞然後返回一個Future
  4. printDailyNewsDigest使用then指定Future的響應函式。呼叫then()返回的新Future將使用then指定回撥返回的值完成。
  5. 剩下的列印函式同步執行,因為他們是同步的
  6. 當所有新聞到達,gatherNewsReports返回的Future帶著一個包含新聞的字串完成
  7. 在printDailyNewsDigest中then函式指定的回撥函式執行。列印新聞
  8. 程式退出

在printDailyNewsDigest函式中then可以多種不同的方式寫。

  1. 使用花括號。當有多個操作的時候,這種方式比較方便
printDailyNewsDigest() {
final future = gatherNewsReports();
future.then((news) {
  print(news);
  // Do something else...
});
}
  1. 直接傳print函式
printDailyNewsDigest() =>
  gatherNewsReports().then(print);

錯誤處理

使用Future介面,你可以使用catchError捕獲錯誤

printDailyNewsDigest() =>
    gatherNewsReports()
        .then((news) => print(news))
        .catchError((e) => handleError(e));

如果新聞不可用,那麼程式碼的執行流程如下:

  1. gatherNewsReports的Future帶一個Error完成。
  2. then的Future帶一個Error完成。
  3. catchError的回撥處理錯誤,catchError的Future正常結束,錯誤不在被傳遞。

當使用Future介面時,在then後連線catchError,可以認為這一對介面相應於try-catch。

像then(), catchError返回一個帶回調返回值的新Future

使用then連線多個函式

當Future返回時需要順序執行多個函式是,可以串聯then呼叫

expensiveA()
    .then((aValue) => expensiveB())
    .then((bValue) => expensiveC())
    .then((cValue) => doSomethingWith(cValue));

使用Future.wait等待多個Future完成

當wait的所有Future完成時,Future.wait返回一個新的Future。這個Future帶了一個包含所有等待結果的列表。

Future
    .wait([expensiveA(), expensiveB(), expensiveC()])
    .then((List responses) => chooseBestResponse(responses))
    .catchError((e) => handleError(e));

當任意一個呼叫以error結束時,那麼Future.wait也以一個Error結束。使用catchError處理錯誤

參考: https://www.dartlang.org/tutorials/language/futures