민프

[Flutter-Error] Dart에서 Future 취소하는 방법(CancelableOperation) 본문

Frontend/[Flutter Error]

[Flutter-Error] Dart에서 Future 취소하는 방법(CancelableOperation)

민프야 2025. 3. 20. 19:00

이번 포스팅에서는 비동기 작업을 중간에 취소하는 방법 중 하나인 CancelableOperation을 소개하려고 합니다. 특히 BLoC(Cubit) 패턴에서 비동기 작업이 중첩되거나 화면 전환 시 작업이 취소되지 않아 발생하는 문제를 해결할 수 있는 좋은 방법입니다.


문제상황

Exception has occurred. StateError (Bad state: Cannot emit new states after calling close)

 

최근에 HomeCubit에서 게시글 목록을 불러오는 작업을 하던 중, 화면을 빠르게 이동하거나 여러 번 loadPosts()를 호출하면 이전 요청이 취소되지 않고 계속 실행되는 문제를 발견했습니다. 이 문제를 해결하기 위해 Flutter의 async 패키지에서 제공하는 CancelableOperation을 활용하게 되었습니다.

 


CancelableOperation이란?

 

CancelableOperation은 Future와 비슷하지만, Future와 다르게 중간에 작업을 취소할 수 있는 기능을 제공합니다. 기존의 Future는 await 중에는 취소가 불가능합니다. 반면, CancelableOperation은 아래처럼 cancel() 메서드를 제공하여 작업을 중단시킬 수 있습니다.


코드적용

    class HomeCubit extends Cubit<HomeState> {
      HomeCubit() : super(const HomeState());

      CancelableOperation<void>? _loadOperation;

      @override
      Future<void> close() {
        _loadOperation?.cancel();
        return super.close();
      }

      Future<void> loadPosts() async {
        if (_loadOperation != null) {
          await _loadOperation?.cancel();
        }

        try {
          emit(state.copyWith(status: HomeStatus.loading));

          _loadOperation = CancelableOperation.fromFuture(
            Future.delayed(const Duration(seconds: 1)),
          );

          await _loadOperation?.value;

          final mockPosts = _generateMockPosts();
          final mockTeamPosts = _generateMockTeamPosts();

          emit(state.copyWith(
            status: HomeStatus.loaded,
            recentPosts: mockPosts,
            teamPosts: mockTeamPosts,
            ideaPosts: mockPosts.where((post) => post.type == PostType.idea).toList(),
            qnaPosts: mockPosts.where((post) => post.type == PostType.qna).toList(),
            profilePosts: mockPosts.where((post) => post.type == PostType.profile).toList(),
          ));
        } catch (e) {
          if (e is! CancelableOperation<void>) {
            emit(state.copyWith(
              status: HomeStatus.error,
              error: e.toString(),
            ));
          }
        } finally {
          _loadOperation = null;
        }
        
        .
        .
        .

참고링크

https://pub.dev/documentation/async/latest/async/CancelableOperation-class.html

Comments