Dart-01: Codelabs
Dart-01-01   Intro to Dart for Java Developers
Dart-01-02   Dart Cheatsheet
Dart-01-03   Iterable Collections
Dart-01-04   Asynchronous Programming
Dart-01-05   Null Safety

Asynchronous Programming

Asynchronous Programming

1. Why asynchronous code matters

비동기(Asynchronous) 작업을 사용하면 다른 작업이 완료되기를 기다리는 동안 프로그램이 작업을 완료할 수 있다. 아래는 몇 가지 일반적인 비동기 작업이다.

  • 네트워크를 통해 데이터를 가져온다.
  • 데이터베이스에 쓴다.
  • 파일로부터 데이터를 읽는다.

Dart에서 비동기 작업을 수행하려면, Future class와 asyncawait keyword를 사용한다.


다음 예시는 비동기 함수 (fetchUserOrder())를 사용하는 잘못된 방법을 보여준다. 나중에 asyncawait를 사용하여 이 예제를 수정한다.

// This example shows how *not* to write asynchronous Dart code.

String createOrderMessage() {
    var order = fetchUserOrder();
    return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
        const Duration(seconds: 2),
        () => 'Large Latte',
    );

void main() {
    print(createOrderMessage());
}

/* result
Your order is: Instance of '_Future<String>'
*/

예를 들어 fetchUserOrder()가 결국 생성하는 값을 출력하지 못하는 이유는 다음과 같다.

  • fetchUserOrder()는 delay 이후, 사용자의 주문을 설명하는 문자열인 “Large Latte”를 제공하는 비동기 함수이다.

  • 사용자의 주문을 가져오려면, createOrderMessage()fetchUserOrder()를 호출하고 완료될 때까지 기다려야 한다. createOrderMessage()fetchUserOrder()가 완료될 때까지 기다리지 않기 때문에, createOrderMessage()fetchUserOrder()가 결국 제공하는 문자열 값을 가져오지 못한다.

  • 대신, createOrderMessage()는 완료되지 않은 미래의 보류중인 작업을 나타낸다.

  • createOrderMessage()는 사용자의 주문을 설명하는 값을 가져오지 못하기 때문에, 예시는 console에 “Large Latte”를 출력하는데 실패하고, 대신 “Your order is: Instance of ‘_Future$<$String$>$’“을 출력한다.

다음 section에서는 fetchUserOrder()가 원하는 값(“Large Latte”)을 console에 출력하도록 하는 데 필요한 코드를 작성할 수 있도록, futures와 futures 작업 (asyncawait 사용)에 대해 배운다.


  • 동기 작업(synchronous operation): 동기 작업은 완료될 때까지 다른 작업의 실행을 차단한다.
  • 동기 함수(synchronous function): 동기 함수는 동기 작업만 수행한다.
  • 비동기 작업(Asynchronous operation): 일단 시작되면, 비동기 작업을 통해 완료되기 전에 다른 작업을 실행할 수 있다.
  • 비동기 함수(Asynchronous function): 비동기 함수는 하나 이상의 비동기 작업을 수행하며 동기 작업도 수행할 수 있다.

2. What is a future?

future(소문자 “f”)는 Future(대문자 “F”) class의 instance이다. future는 비동기 작업의 결과를 나타내며, uncompleted 또는 completed의 두 가지 상태를 가질 수 있다.


Uncompleted: 비동기 함수를 호출하면, uncompleted future를 return한다. 그 future는 함수의 비동기 작업이 완료되거나 error를 throw 하기를 기다리고 있다. (값을 생산하기 전의 future 상태를 나타내는 Dart 용어)


Completed: 비동기 작업이 성공하면, future가 어떤 값으로 완료된다. 그렇지 않으면, error와 함께 완료된다.

  • Completing with a value: Future<T> type의 future는 T type의 값으로 완료된다. 예를 들어, Future<String> type의 future는 string 값을 생성한다. future가 사용 가능한 값을 생성하지 않는다면, future의 type은 Future<void>이다.

  • Completing with an error: 함수에 의해 수행된 비동기 작업이 어떤 이유로든 실패하면, future는 error와 함께 완료된다.


아래 예시에서, fetchUserOrder()는 console 출력 후 완료되는 future를 return한다. 사용 가능한 값을 return 하지 않기 때문에, fetchUserOrder() type은 Future<void>이다.

아래 예시에서, fetchUserOrder()가 8행의 print() 호출 전에 실행되더라도, console은 fetchUserOrder()의 출력(“Large Latte”) 이전에 8행(“Fetching user order…“)의 출력을 표시한다. 이는 fetchUserOrder()가 “Large Latte”를 출력하기 전에 delay 되기 때문이다.

Future<void> fetchUserOrder() {
    // Imagine that this function is fetching user info from another service or database.
    return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}

void main() {
    fetchUserOrder();
    print('Fetching user order...');
}

/* result
Fetching user order...
Large Latte
*/

아래 예시에서 future가 error와 함께 어떻게 완료되는지 확인할 수 있다. 그 이후에는 error를 어떻게 처리하는지도 알아볼 것이다.

아래 예시에서 fetchUserOrder()는 user ID가 유효하지 않음을 나타내는 error와 함께 완료된다.

future와 future가 어떻게 완료되는지 배웠지만, 비동기 함수의 결과를 어떻게 사용하는지는 아직 모른다. 다음 section에서 asyncawait keyword로 결과를 얻는 방법을 배운다.

Future<void> fetchUserOrder() {
    // Imagine that this function is fetching user info but encounters a bug
    return Future.delayed(const Duration(seconds: 2),
        () => throw Exception('Logout failed: user ID is invalid'));
}

void main() {
    fetchUserOrder();
    print('Fetching user order...');
}

/* result
Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid
*/

3. Working with futures: async and await

asyncawait keyword는 비동기 함수를 정의하고 결과를 사용하는 선언적 방법을 제공한다. asyncawait를 사용할 때 다음 두 가지 기본 지침을 기억해야 한다.

  • 비동기 함수를 정의하려면, 함수 body 앞에 async를 추가한다.
  • await keyword는 async 함수에서만 작동한다.

아래는 동기 함수에서 비동기 함수로 main()을 변화하는 예시이다.

먼저, 함수 body 앞에 async keyword를 추가한다.

void main() async { ... }

함수에 선언된 return type이 있는 경우, type을 Future<T>로 update 한다. 여기서 T는 함수가 return하는 값의 type이다. 함수가 명시적으로 값을 return하지 않으면, return type은 Future<void>이다.

Future<void> main() async { ... }

이제 async 함수가 있으므로, await keyword를 사용하여 future가 완료될 때까지 기다릴 수 있다.

print(await createOrderMessage());

아래 두 예시에서 볼 수 있듯이, asyncawait keyword는 동기 code와 매우 유사한 비동기 code를 생성한다.

// synchronous functions

String createOrderMessage() {
    var order = fetchUserOrder();
    return 'Your order is: $order';
}

Future<String> fetchUserOrdre() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
        const Duration(seconds: 2),
        () => 'Large Latte',
    );

void main() {
    print('Fetching user order...');
    print(createOrderMessage());
}

/* result
Fetching user order...
Your order is: Instance of '_Future<String>'
*/
// asynchronous functions

Future<String> createOrderMessage() async {
    var order = await fetchUserOrder();
    return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
        const Duration(seconds: 2),
        () => 'Large Latte',
    );

Future<void> main() async {
    print('Fetching user order...');
    print(await createOrderMessage());
}

/* result
Fetching user order...
Your order is: Large Latte
*/

비동기식 예제는 세 가지 면에서 다르다.

  • createOrderMessage()의 return type은 String에서 Future<String>으로 바뀐다.
  • async keyword는 createOrderMessage()main()의 함수 body 앞에 나타난다.
  • await keyword는 비동기 함수 fetchUserOrder()createOrderMessage()를 호출하기 전에 나타난다.

async 함수는 첫 번째 await keyword 까지 동기적으로 실행된다. 이는 비동기 함수 body 내에서, 첫 번째 await keyword 이전의 모든 동기 code가 즉시 실행됨을 의미한다.

Future<void> printOrderMessage() async {
    print('Awaiting user order...');
    var order = await fetchUserOrder();
    print('Your order is: $order');
}

Future<String> fetchUserOrder() {
    // Imagine that this function is more complex and slow.
    return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}

void main() async {
    countSeconds(4);
    await printOrderMessage();
}

// You can ignore this function - it's here to visualize delay time in this example.
void countSeconds(int s) {
    for (var i = 1; i <= s; i++) {
        Future.delayed(Duration(seconds: i), () => print(i));
    }
}

/* result
Awaiting user order...
1
2
3
4
Your order is: Large Latte
*/
// if
Future<void> printOrderMessage() async {
    var order = await fetchUserOrder();
    print('Awaiting user order...');
    print('Your order is: $order');
}

/* result
1
2
3
4
Awaiting user order...
Your order is: Large Latte
*/

4. Handling errors

async 함수의 오류를 처리하려면, try-catch를 사용한다.

async 함수 내에서, 동기 code에서와 같은 방식으로 try-catch 절을 작성할 수 있다.

try {
    print('Awaiting user order...');
    var order = await fetchUserOrder();
} catch (err) {
    print('Caught error: $err');
}
Future<void> printOrderMessage() async {
    try {
        print('Awaiting user order...');
        var order = await fetchUserOrder();
        print(order);
    } catch (err) {
        print('Caught error: $err');
    }
}

Future<String> fetchUserOrder() {
    // Imagine that this function is more complex.
    var str = Future.delayed(
        const Duration(seconds: 4),
        () => throw 'Cannot locate user order');
    return str;
}

void main() async {
    await printOrderMessage();
}

/* result
Awaiting user order...
Caught error: Cannot locate user order
*/

태그:

카테고리:

업데이트:

댓글남기기