DAY 06 | Dart 언어의 asynchronous task를 살펴보자

Pt J·2026년 7월 10일
post-thumbnail

DAY 06 | Dart 언어의 asynchronous task를 살펴보자

코딩하고는 무관합니다만,

오전에는 class의 constructor에 대한 걸 다시 짚고 넘어가다가, 코딩과는 살짝 무관한, 하지만 SeSAC과 유관한 이야기를 했다. 아무튼 우리는 취업(혹은 창업)을 해야 하니, 어떻게 준비할 것인가. 이력서 포맷은 크게 중요한 건 아니니까 일단 준비해 보고 너무 아니다 싶으면 적당한 포맷을 추천해 주신다고 하시길래 처음부터 적당한 포맷을 추천해 주실 수 있는지 여쭤봤다. 기존에 만들어서 개선해 온 게 있는 사람은 그걸 쓰면 되지만, 이제 막 시작하는 사람 입장에서는 엄한 것보다는 적당한 것을 쓰는 게 나을 테니까.

사진이나 포맷 같은 건 엄청 중요한 건 아니지만 우리는 0.1점의 차이도 무시하지 않겠다는 태도로 준비해야 한다고. 스펙이 그걸 압도할 수준이 되면 괜찮지만 우리는 집요하게 다듬어야 한다고 하시는데 무슨 말인지는 알 것 같다.

아무튼 틈틈이 이력서/포트폴리오/자기소개서를 준비해 보자. Flutter랑은 무관하지만 요즘 한창 준비 중인 니트컴퍼니 전시의 방명록 시각화 웹 페이지도 포트폴리오에 넣고 해야지. (전시장 입구에서 사용자가 /register 등록 화면에 접속하여 등록하면 전시장 내부에 있는 / 전광판에 그 사람의 정보가 뜨고, 비밀번호 입력해서 들어가는 /admin 대시보드에서 시간대별 방문자 수를 확인할 수 있는 바이브코딩 프로젝트다. 어제 오후에 스탭 회의에서 말 나와서 어제 밤에 만들고 오늘 아침에 배포해 봤다. 그리고 오늘 점심에 스탭 채팅방에 피드백 달라고 링크 공유했는데 아직 보완 의견은 없다.)

분명 "어떤 앱 만들고 싶은지 주말동안 생각해 오자" 한지 얼마 안 된 것 같은데 벌써 또 "자기소개서에 어필하고 싶은 장점 다섯 가지 주말동안... 벌써 주말이야?????" 하고 있다.

class를 마저 살펴보고 넘어간다

지난 시간에는 constructor만 살펴 보았다. property에 대해 좀 더 알아보자.

모든 property는 기본적으로 private으로 작성하는 게 객체지향의 원칙이다. encapsulation 관점에서 그게 맞다. 하지만 Dart는 public을 기본으로 한다. 모든 property에 getter와 setter를 설정할 거면 그냥 public으로 하는 게 낫다는 입장이다.

다음과 같은 property에 대해 getter/setter를 사용하면

class User {
  // 다른 property 생략
  int _age;
  // constructor 및 method도 생략
}

getter로는 이렇게 그냥 가져가면서

int get age => _age;

setter로 값을 설정할 땐 유효성 검사를 할 수 있다.

set age(int age) {
	(age > 0) ? _age = age : print('Invalid age');
}

Dart의 getter/setter는 method가 아닌 property다. 다음과 같이 property 접근하듯 사용한다. 정의할 땐 method와 유사하게 생겼지만 사용하는 입장에서 property처럼 사용하기에 property로 취급한다.

User anon = User();
user.age = 30;
print(user.age);

터지지 않게 error handling

코드가 실행되다가 error 상황을 마주치면 crash되며 중단된다. 이를 막을 수 있는 가장 일반적인 방법은 error handling이다. error 상황을 마주치면 적절한 조치를 취하는 거다.

성공/실패 경우를 모두 처리하지 않으면 컴파일 자체가 되지 않아, error 처리를 누락하는 실수를 원천 차단하는 Rust에 익숙한 사람은 잘 마주치지 않는 로직이다. 복구 가능한 error는 Result<T, E> 로 작성하고 그렇지 않은 error는 panic! 으로 작성하여 error를 처리하면 흐름 제어를 보다 명확하게 할 수 있다. 하지만 Dart를 비롯한 대부분의 언어들은 try-catch 를 통해 error를 처리한다.

try {
  // 작업을 시도한다
  TASK1;
  TASK2;
  TASK_WITH_ERROR; // 여기서 error 발생!
  TASK3; // 이건 실행되지 않는다
} on FormatException {
  // 숫자 써야 하는데 문자 쓴다거나 할 때 발생한 error 처리
} on IntegerDivisonByZeroException {
  // 0으로 나누는 나눗셈 시도 시 발생한 error 처리
} catch (e) {
  // 앞에서 errorType으로 필터링되지 않은 error 처리
} finally {
  // error 발생 여부와 별개로 항상 실행되는 영역
}

on 뒤에 올 수 있는 error type은 다양한데 모든 걸 외우고 있진 않아도 된다. 필요할 때 "이런 error에 대한 error type은 뭐라고 부르지?" 하고 찾아가며 작성해도 충분하다.

내부적으로 error 발생이 구현되어 있는 코드에 대해서는 위와 같이 구현하면 되고, 그 외 특정 상황에서 error handling을 하고자 한다면 throw Exception('임의의 error message'); 하여 적절한 error message를 띄울 수 있다. Excpetion class를 상속받아 catch (e) {} 에서 이에 대한 처리를 구현할 수도 있다.

순차적으로 실행되지 않는 영역

그냥 작성한 코드는 synchronous하게 실행된다. 순차적으로 실행된다는 것이다. 하지만 오래 걸리는 작업을 마냥 기다리는 건 비효율적인 일이다. 때로는 asynchronous하게 실행하여 병렬적인 작업을 수행할 필요가 있다.

병렬? 비동기?

순차적으로 실행하지 않는 것은 concurrent와 parallel이 있다.
concurrent는 한 녀석이 여러 작업을 번갈아가며 수행하는 것이고 parallel은 여러 녀석이 각자의 작업을 수행하여 전체로 봤을 때 여러 작업이 동시에 실행되는 것이다.
concurrent하면서도 parallel한 경우에는 여러 녀석이 여러 녀석을 번갈아가며 수행하는 것이다.

Dart에서는 function에 async 키워드를 붙여 asynchronous function으로 만들 수 있다.

Future<TYPE> FUNCTION_NAME(TYPE PARAMETER) async {
  TASK;
}

asynchronous와 syncrhonous가 섞여 있다면 synchronous가 먼저 실행된다고 볼 수 있다. 모든 명령은 최종적으로 하나의 thread로 이루어진 main isolate에서 실행되는데, synchronous는 바로 main isolate로 들어가지만 asynchronous는 event loop의 스케줄링을 거쳐 main isolate에 들어가기 때문이다.

>>> event loop에 대해 더 알고 싶다면

asynchronous function의 return type은 Future<Generic> 이다. return 값이 없어도 void 보다는 Future<void> 로 작성하는 게 좋다. 오류가 나지는 않아도 코드의 명확성과 유지보수성 관점에서 중요하다.

Dart에서 asyncrhonous를 담는 type은 FutureStream 이 있는데, Future 는 한 번의 asynchronous 요청에 대한 응답의 느낌이고, Stream 은 터널을 열어놓고 데이터를 계속 asynchronous로 주고받을 수 있는 느낌이다. Streamasync* 키워드와 yield 키워드를 통해 사용할 수 있는데 여기서는 길게 얘기하지 않겠다. 나중에 Flutter에서 다룰 때 좀 더 자세히 살펴보자.

Future 는 세 가지 상태를 갖는다. 처음에는 Uncompleted 상태로 존재하다가 asynchronous 처리가 완료되면 Completed 상태로 변경된다. Completed 상태는 Completed with a value 상태와 Completed with an error 상태로 나뉜다.

  • Uncompleted (아직 asynchronous 작업이 끝나지 않음)
  • Completed
    • Completed with a value (asynchronous 작업이 정상 종료됨)
    • Completed with an error (asynchronous 작업 중 오류 발생)

Completed 상태의 Future 는 asynchronous 작업의 결과값 또는 error 데이터를 가지고 있다.

Completed with a value 상태의 Future instance는 then() method를 통해 사용할 수 있다. 중간에 error가 발생하여 Completed with an error 상태로 전달된 Future instance는 then() method를 무시하고, catchError() 를 통해 처리할 수 있다. 반대로 Completed with a value 상태로 전달되어 then() method가 실행된 경우 catchError() method는 무시된다.

error handling에서 error 발생 여부와 상관 없이 finally 코드 블록이 실행되듯이, Completed 상태의 Future 는 error 발생 여부와 상관 없이 whenCompleted() method를 사용하여 공통 작업을 수행할 수 있다.

futureInstance
  .then((value) {
    TASK;
  })
  .catchError((error) {
    ERROR_HANDLING;
  })
  .whenCompleted(() {
    DO_ALWAYS;
  });

실습을 하자

concurrency는 intermediate 수준의 내용이기도 하고, 개념은 알고 있지만 내가 많이 안 다뤄본 부분이기도 해서 실습을 진행했다. 개념을 이해하는 데 시간이 걸리는 영역이라 intermediate 수준인 거지 개념을 이해하고 있으면 코드를 작성하는 건 어렵지 않았다.

이력서/자기소개서/포트폴리오에 대한 가이드(정답은 아니고 제안? 참고사항?)를 받았으니 주말에 좀 준비해 봐야겠다. 내일은 청년정책박람회에서 하는 특강 두 개 선정되어서 거기 다녀올 거고 일요일쯤 여유가 되려나. 계속 미루면 어느 순간 한참 지나 있으니 이번 주말에는 조금이라도 건드려 보도록 하자!

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글