이번 글은 'Secrets of the JavaScript Ninja'의 'Chapter 6. Funtions for the future: generators and promises'를 바탕으로 작성하였습니다.
핵심 Concepts
- 제너레이터를 활용한 끊임 없는 함수 실행
- 프로미스를 활용한 비동기적(asynchronous) 작업 수행
- 제너레이터와 프로미스를 합쳐 유려한 비동기적 코드 작성
사전 지식 체크!
- 제너레이터 함수의 일반적인 사용에는 어떤 것들이 있는가?
- 비동기적 코드를 구현하는 데 일반적인 콜백보다 프로미스가 더 나은 이유?
- Promise.race.를 활용하여 긴 코드 작업을 수행할 때 프로미스는 언제 해결되는가? 그리고 언제 해결되는 데 실패하는가?
Working with promises
JavaScript에서 작업할 때 계산 결과값이 현재 산출되지 않았으나 나중 언젠가 산출되는 비동기적 계산(asynchronous computations)에 의존한다. 이러한 비동기적 작업을 원활히 하기 위해 ES6에서 프로미스가 도입되었다.
프로미스는 현재는 없으나 조만간 갖게될 값을 위한 플레이스홀더(placeholder)이다. 만약 프로미스를 제대로 만들었다면 기대하던 대로 정확한 결과값을 얻을 것이고 아니라면 에러를 반환할 것이다. 프로미스에 대한 좋은 예로는 서버로부터 데이터를 받아오는 작업을 들 수 있다. 이 작업이 끝나면 원하던 값을 얻을 테지만 작업 내내 문제가 일어날 위험성도 상존한다.
다음 예제를 보면 알 수 있듯 새로운 프로미스를 만드는 것은 간단하다.
새로운 프로미스를 만들기 위해 화살표 함수나 함수 표현식을 전달하는 내재된 Promise 생성자를 사용한다. 이 함수를 이그제큐터(executor) 함수라고 하며 resolve와 reject라는 두 가지 매개변수를 갖는다. 이그제큐터는 두 개의 내재 함수를 인수로 받는 Promise 객체가 생성됨과 동시에 호출된다. 두 개의 내재 함수 중 하나는 resolve로 프로미스가 성공적으로 실행되면 호출되고, reject는 에러가 발생하면 호출된다.
위 코드는 Promise 객체의 내재된 메소드인 then을 호출함으로써 프로미스 기능을 구현했다. 이 메소드는 success와 failure라는 두 가지 콜백 함수를 전달한다. 전자는 프로미스에서 resolve 함수가 호출될 때 호출되며 후자는 reject 함수가 호출될 때 호출된다.
위 예제에서는 프로미스를 만들어 'Hattori'라는 인수를 갖는 resolve 함수를 호출함으로써 즉시(immediately) 해결된다. 그러므로 then 메소드를 호출하면 처음 콜백인 success가 실행되어 결과값으로 'We were promised Hattori!'가 반환된다.
Understanding the problems with simple callbacks
작업 시간이 긴 애플리케이션을 실행할 때 실행을 방해하지 않기 위해서 비동기적으로 코드를 작성한다. 앞으로 이 문제를 콜백을 활용하여 해결해보겠다.
예를 들어 서버에서 JSON 파일을 가져온다고 해보자. 이 작업은 매우 긴 작업이고 작업이 이루어지고 있는 동안 애플리케이션의 반응이 끊겨서는 안 될 것이다. 그러므로 작업이 완료될 때 호출되는 콜백을 제공하여 이 같은 문제를 해결한다.
작업을 수행하는 도중 에러가 일어날 가능성은 상존한다. 그리고 콜백에 관한 문제는 바로 try-catch문 같은 내재된 언어 구조를 사용하지 못한다는 점이다.
왜냐하면 콜백을 호출하는 코드는 일반적으로 장기 실행 작업을 시작하는 코드와 동일한 이벤트 루프 단계에서 실행되지 않기 때문이다.
결과적으로 에러가 반환되지 않는다. 대부분의 라이브러리는 에러를 보고하는 문법을 갖고 있다. 예를 들어 Node.js의 경우 콜백은 err와 data라는 두 가지 인수를 갖는데 여기서 err는 도중에 에러가 발생하면 null이 아닌 값이 된다. 이 때문에 에러를 다루기 힘들다는 콜백의 첫 번째 문제점이 생긴다.
기나긴 작업이 끝나고 나서 이렇게 얻어진 데이터로 어떤 작업을 하고 싶을 것이다. 이는 또 다른 상호 의존적이고 비동기적이며 작업 시간이 긴 콜백 처리 단계로 이어짐을 뜻한다. 아래 예제를 살펴 보자.
위 예제 코드를 보면 알겠지만 코드가 무더기로 중첩되어 있어서 알아보기도 힘들고 새로운 단계를 삽입하는 것도 까다로워서 에러가 발생해도 처리하기 어렵다. 이는 두 번째 문제점을 야기한다. 바로 연속된 단계를 수행하는 것이 까다롭다는 점이다.
때때로 최종 결과값을 얻기 위해서 거쳐야 하는 과정은 서로 연관되어 있지 않기도 하다. 따라서 굳이 연속적으로 만들지 않고 병렬(parallel) 처리 형식으로 만들어도 된다. 허나 병렬 처리 작업을 수행하는 코드를 작성 시에 문제점이 발생한다. 바로 데이터를 수신할 시 무작위로 전해지기 때문에 각 데이터가 마지막 데이터인지 확인을 해야 한다. 모든 데이터 조각이 모이고 나서야 작업이 시작되는 것이다. 따라서 병렬 처리 작업 하에서 각 데이터를 수집하기 위해 수많은 보일러플레이트(boilerplate)를 적어야만 한다. 이것이 바로 세 번째 문제인 여러 단계의 병렬 처리 작업을 수행하기 까다롭다이다.
이러한 문제들을 해결하기 위해서 라이브러리를 만들 수도 있을 것이다. 허나 JavaScript에서는 비동기적 계산을 다루는 데 유용한 프로미스라는 기능을 제공한다.
이제까지 프로미스가 도입된 배경에 대해 알아보았다. 이 같은 지식을 바탕으로 프로미스에 대해 한층 더 깊이 알아보자.
Diving into promises
프로미스는 비동기적 과업의 결과를 위한 플레이스홀더이다. 프로미스는 아직 갖고 있지 않으나 앞으로 가질 것으로 예상되는 값을 나타낸다. 프로미스는 아래 그림에 나와 있듯 몇 가지 단계를 거쳐 작동한다.
프로미스는 아직 약속된(promised) 값을 알 수 없는 pending(미결, 未決) 상태에서 시작한다. 때문에 pending 상태는 미해결(unresolved) 상태라고도 한다. 프로그램이 실행되는 동안 프로미스의 resolve 함수가 호출되면 프로미스는 성공적으로 약속된 값을 얻은 fullfilled 상태로 옮아간다. 반면 reject 함수가 호출되거나 예외가 발생하면 rejected 상태로 옮아간다. 이 상태에서는 약속된 값을 얻을 수는 없지만 적어도 얻지 못하는 이유는 알 수 있다.프로미스가 fullfilled 상태 혹은 rejected 상태에 이르게 되면 변경이 불가능하여 언제까지고 그 상태에 머문다. 이와 같은 상태에 이르면 프로미스가 해결되었다(resolved)고 말한다.
위 예제 코드를 실행하게 되면 다음과 같은 결과값이 출력된다.
예제 코드는 "At code start"라는 메시지를 남기면서 시작한다. 그 다음 Promise 생성자를 호출하여 새로운 프로미스를 만든다. 이는 timeout을 설정한 이그제큐터 함수를 즉시 호출한다. timeout은 500ms 후에 프로미스를 해결(resolve)한다. 이후 아직 값을 갖고 있지 않으나 추후 갖게 될지 혹은 성공적으로 작업이 수행될지 모르는 ninjaDelayedPromise가 생성된다.(이는 timeout이 해결해주길 기다리고 있기 때문이다.) 생성을 끝마치고 나면 ninjaDelaytedPromise는 pending 상태에 들어간다.
그 다음 ninjaDelayedPromise에서 프로미스가 성공적으로 해결될 경우 실행될 콜백을 then 메소드를 사용하여 설정한다. 이 콜백은 프로미스의 현재 상태와 상관 없이 언제나 비동기적으로 호출된다.
다음 작업으로 또 다른 프로미스인 ninjaImmediatePromise를 생성한다. 이 프로미스는 resolve 함수를 호출하여 생성되자마자 해결된다. 생성된 후 pending 상태에 들어가는 ninjaDelayedPromise와는 달리 ninjaImmediatePromise는 resolved 상태에 들어가며 해당 프로미스는 이미 'Yoshi'라는 값을 가진다.
그 이후 프로미스가 성공적으로 해결된 때 실행될 콜백을 등록하기 위해 ninjaImmediatePromise의 then 메소드를 사용한다. 하지만 이 프로미스는 이미 안정되어 있다. 그렇다면 성공 콜백이 즉시 호출되는 것일까 아니면 무시되는 것일까? 정답은 둘 다 아니다.
프로미스는 비동기적 동작을 다루기 위해 설계되어서 JavaScript 엔진은 항상 프로미스의 작동이 예측 가능하게 만들기 위해 비동기적 조작에 의존한다. 이벤트 루프의 현재 단계 안에 있는 모든 코드가 실행되고 나서 then 콜백을 실행함으로써 엔진은 이 같은 작업을 수행한다. 따라서 Figure 6.11의 결과값을 살펴 보면 'At code end'가 먼저 기록되고 나서 ninjaImmediatePromise가 해결되는 것을 볼 수 있다. 이윽고 500ms가 지나고 ninjaDelayedPromise가 해결되어 일치하는 then 콜백을 실행한다.
Rejecting promises
프로미스를 거절하는(rejecting) 데는 두 가지 방법이 있다. 하나는 명시적인(explicitlye) 방법으로 reject 메소드를 프로미스의 이그제큐터 함수 안에서 사용하는 것이며 또 하나는 암묵적인(implicitly) 방법으로 프로미스가 작동되는 동안 다뤄지지 않은 예외가 발생하는 경우이다. 다음 예제를 통해 살펴보자.
reject 메소드를 호출함으로써 명시적으로 프로미스를 거절할 수 있다. 프로미스가 거절되면 then 메소드를 통해 등록된 두 번째 콜백 함수 error가 호출된다. 이 외에도 프로미스의 거절(rejections)을 다루기 위해 내재된 catch 메소드를 사용할 수 있다.
위 예제 코드에 나오듯 then 메소드 뒤에 catch 메소드를 연달아 쓸 수 있다. 이로써 프로미스가 거절될 때 호출되는 에러 콜백을 입력할 수 있다. 위에 살펴본 예제 코드 중 어떤 코드를 쓸지는 개인 취향에 달렸다. 허나 프로미스 체인으로 작업할 시에는 catch 메소드가 더 유용하다.
명시적으로 거절되는 방법 이외에도 프로미스는 실행 중에 예외가 발생할 경우 암묵적으로 거절된다. 이에 해당하는 예제를 살펴보자.
프로미스 실행자의 내용을 보면 정의되지 않은 undeclaredVariable을 증가시키려고 한다. 예상하는 대로 이 프로미스의 결과는 예외이다. 왜냐하면 위 코드 어디에도 try-catch문이 없어 현재 프로미스의 결과는 암묵적인 거절이 되고 catch 콜백이 호출된다. 이 같은 상황에서 then 메소드에다 두 번째 콜백을 쉽게 제공할 수 있고 최종 효과는 동일할 것이다.
프로미스로 작업하는 동안 발생하는 모든 문제를 균일한 방식으로 처리하는 이 방법은 매우 편리하다. 프로미스가 얼마나 거절되든 상관없이 명시적이든 암묵적이든 가리지 않고 예외가 발생하면 에러와 함께 거절된 이유가 거절 콜백으로 전달된다.
Creating our first real-world promise
가장 흔한 비동기적 동작 중 하나는 서버에서 데이터를 가져오는 것이다. 이를 위해 내재된 XMLHttpRequest 객체를 사용한다. 예제를 살펴보자.
위 예제 코드의 목표는 getJSON 함수를 만드는 것이다. 이로써 서버로부터 비동기적으로 JSON 형식의 데이터를 받기 위한 성공(success)와 실패(failure) 콜백을 등록하게 해주는 프로미스를 반환한다. 근본적인 실행을 위해 내재된 XMLHttpRequest 객체를 사용하는데 이 객체는 두 가지 이벤트를 제공한다. 하나는 onload로서 브라우저가 서버로부터 응답(response)를 받았을 시 호출되며 다른 하나는 onerror로서 커뮤니케이션 생겼을 때 에러가 발생하면 호출된다. 이러한 이벤트 핸들러는 브라우저에 의해 비동기적으로 호출된다.
만약 커뮤니케이션 과정에 에러가 생겨 서버로부터 데이터를 받을 수 없다면 프로미스는 거절된다. 반면에 서버로부터 응답을 받는다면 응답을 면밀히 살펴보아야 하고 적확한 상황도 고려해야 한다. 응답의 종류는 매우 다양하지만 더 자세한 사항은 나중에 알아보도록 하고 여기서는 일단 응답이 성공적으로 완료된 경우(status 200)만을 고려하겠다. status 200이 아닌 경우 역시 프로미스는 거절된다.
서버로부터 성공적으로 데이터를 받았다고 해서 모든 게 완벽하다는 뜻은 아니다. 우리의 목표는 바로 JSON 형식의 객체를 서버로부터 받는 것이며 JSON 코드는 항상 구문에 오류(syntax error)가 있기 때문이다. 이것이 바로 JSON.parse 메소드를 호출할 때 try-catch문으로 코드를 감싸는 까닭이다. 서버 응답을 파싱(parsing)하는 도중 예외가 발생하면 프로미스는 거절된다. 따라서 최악의 경우까지 상정하여 코드를 작성해야 한다.
만약 모든 것이 계획대로 진행되어 성공적으로 객체를 얻었다면 프로미스가 무사히 해결된다. 마침내 서버로부터 ninjas를 받기 위해 getJSON 함수를 사용할 수 있는 것이다.
위 예제에는 오류가 잠재되어 있다. 서버와 클라이언트 간의 커뮤니케이션에서 발생하는 에러, 예상치 못한 데이터로 응답하는 서버(잘못된 응답 상태), 유효하지 않은 JSON 코드 등이다. 허나 getJSON 함수를 쓰는 코드 측면에서 보면 에러의 세세한 부분까지는 신경 쓰지 않아도 된다. 단지 데이터를 제대로 수신했을 때와 그러지 못 했을 때의 콜백만 제공하면 된다.
Chaining promises
프로미스는 연결해서(chain) 작성 가능하기 때문에 콜백 함수를 중첩하여 작성하는 것을 방지할 수 있다. 앞서 then 메소드를 활용하여 프로미스가 성공적으로 해결되면 실행되는 콜백을 등록하는 방법을 살펴보았다. 하나 더 알아두어야 할 점은 then 메소드를 호출하면 새로운 프로미스가 반환된다는 점이다. JavaScript 코드를 작성함에 있어 then 메소드를 원하는 만큼 연달아 적는 데 방해하는 요소는 아무 것도 없다. 다음 예제를 살펴보자.
위 예제 코드는 계획대로 모든 것이 순조롭게 진행된다면 하나에 이어 또 하나가 해결되는 연속된 프로미스를 생성한다. 첫 번째로 getJSON("data/ninjas.json") 메소드를 사용하여 서버 상의 파일로부터 ninjas의 목록을 받아온다. 리스트를 받고 나서 첫 번째 닌자에 대한 정보를 취하고 getJSON(ninjas[0].missionsUrl) 메소드로써 닌자가 부여받은 임무의 목록을 요청한다. 이후 미션이 들어오면 첫 번째 임무에 대한 세부 사항을 getJSON(missions[0]/detailsUrl) 메소드로 요청한다. 이윽고 임무의 세부 사항이 기록에 남는다.
Catching errors in chained promises
만약 이전의 어떤 프로미스에서 실패가 일어난다 해도 catch 메소드는 에러를 잡아낸다. 에러가 없다면 방해 받는 일 없이 프로그램은 진행된다.
프로미스를 활용하여 일련의 절차를 처리하는 게 콜백을 활용하는 것보다 훨씬 낫다는 점을 깨달았을 것이다. 그렇다면 프로미스를 활용하여 병렬적으로 실행되는 비동기 코드를 작성하는 방법을 알아보자.
Waiting for a number of promises
프로미스는 독립된 비동기적 작업을 작성하는 데 부담을 줄여준다. 아래 예제를 통해 병렬 처리적으로 비동기 코드를 작성하는 방법을 알아보자.
코드를 보면 알 수 있듯 여기서 어떤 작업이 먼저 실행되는지, 어떤 작업이 완료되었고 안 되었는지 신경 쓸 필요가 없다. 내장된 Promise.all 메소드를 사용함으로써 여러 개의 프로미스를 기다린다고 언급한다. 이 메소드는 프로미스로 된 배열을 취하고 전달된 모든 프로미스가 해결될 때 성공적으로 해결되는 새로운 프로미스를 생성하고 프로미스 중 하나라도 실패하면 거절된다. 성공한 콜백은 전달된 각 프라미스에 대해 하나씩 성공한 값의 배열을 순서대로 수신한다.
Promise.all 메소드는 리스트 안의 모든 프로미스를 기다린다. 하지만 종종 프로미스는 셀 수 없을 만큼 많기도 하며 이때 성공이거나 실패인 맨 처음 값만을 다룰 때가 있다. 이때 쓰이는 메소드가 바로 Promise.race이다.
Racing promises
우리에게 닌자 무리가 있고 그 중 호출에 응답한 첫 번째 닌자에게 임무를 부여한다고 가정하자. 이러한 프로미스를 다룰 때 아래와 같이 Promise.race 메소드를 사용할 수 있다.
예제 코드를 보면 알겠지만 해당 기능을 구현하기는 매우 간단하다. 일일이 모든 데이터를 쫓을 필요가 없다. Promise.race 메소드를 사용하여 프로미스로 이루어진 배열을 받고 첫 번째 프로미스가 해결되거나 거절되자마자 새로운 프로미스를 생성하여 반환한다.
다음으로는 동기적 코드와 비동기적 코드의 장점만을 취하기 위해 제너레이터와 프로미스를 함께 사용하여 코드를 작성하는 방법을 살펴보겠다.
Combining generators and promises
이번 장에서는 제너레이터의 작업을 중단했다 재개하는 기능을 활용하기 위해 프로미스에다 제너레이터를 합쳐 비동기적 코드를 보다 유려하게 작성하는 법을 알아보겠다.
앞으로 가장 유명한 닌자가 수행한 특등급 임무의 세부 사항을 얻을 수 있게 하는 기능의 예를 살펴볼 것이다. 데이터는 닌자를 나타내고 임무 요약을 제공하며 원격코드에 저장되어 JSON 형식으로 반환되는 임무에 대한 세부 사항이다.
이 작업들은 작업 시간이 길며 상호 의존적이다. 이러한 작업을 동기적 코드로 작성한다면 다음과 같다.
위 코드는 간단하여 알아보기 쉽고 에러 처리하는 데 뛰어나나 데이터를 받아오는 과정에서 UI 렌더링을 방해한다. 이러한 문제점을 고치기 위해 제너레이터와 프로미스를 합친 코드를 사용한다.
알다시피 제너레이터에서 값이 생성되는 것은 제너레이터의 실행을 방해하지 않고 해당 작업을 중단한다. 제너레이터의 작업을 재개하기 위해서는 제너레이터의 반복자에다 next 메소드를 호출해야 한다. 반면 프로미스는 약속된 값을 얻은 경우나 에러가 발생한 경우 호출되는 콜백을 특정한다.
제너레이터와 프로미스를 합치는 기본적인 발상은 다음과 같다. 우선 제너레이터에서 비동기적 과업을 사용하는 코드를 작성하고 제너레이터 함수를 실행한다. 제너레이터 실행에서 비동기적 과업을 실행하는 지점에 달하면 비동기적 과업의 값을 나타내는 프로미스를 만든다. 왜냐하면 제너레이터가 실행되는 시점에서는 프로미스가 언제 해결될지(혹은 해결되더라도) 알 수 없기 때문에 작업이 방해되지 않도록 제너레이터에서 값을 생성한다. 이후 프로미스 작업이 끝나면 반복자의 next 메소드를 호출하여 제너레이터의 실행을 계속한다. 이러한 작업을 필요한 만큼 수행한다.
async 함수는 제너레이터를 받아 호출하고 반복자를 만들어 제너레이터 실행을 재개하는 데 사용한다. async 함수 안에서 handle 함수를 선언하여 제너레이터에서 반환되는 값을 처리한다. 이 값은 반복자에서 반복되어 나오는 하나의 값이다. 만약 제너레이터의 결과가 성공적으로 해결된 프로미스라면 반복자의 next 메소드를 사용하여 약속된 값을 제너레이터에 다시 보내고 제너레이터의 실행을 재개한다. 에러가 일어나서 프로미스가 거절되면 반복자의 throw 메소드를 활용하여 제너레이터에 에러를 보낸다. 이 같은 작업을 제너레이터 내 작업이 완료될 때까지 수행한다.
이제 제너레이터를 자세히 살펴보자. 반복자의 next 메소드가 처음 호출될 때 제너레이터는 첫 번째 getJSON("data/ninjas.json")을 호출한다. 이 호출은 ninjas에 대한 정보 목록을 포함하는 프로미스를 만들어낸다. 왜냐하면 값은 비동기적으로 수신되어 브라우저가 해당 데이터를 얻는 데까지 얼마나 걸릴지 모르기 때문이다. 하지만 하나는 분명히 안다. 작업이 재개되기를 대기하는 동안 애플리케이션 실행이 멈추기를 원하지 않는다는 점이다. 이 같은 이유로 이 실행 시점에서 제너레이터는 제너레이터를 일시 중지하고 handle 함수 호출로 제어 흐름을 반환하는 제어를 제공한다. 산출된 값은 getJSON 프로미스이기 때문에 handle 함수에서는 프로미스의 then과 catch 메소드를 사용하여 성공 및 오류 콜백을 등록하고 계속 실행한다. 이렇게 제어 흐름은 handle 함수와 async 함수의 실행을 떠나고 async 함수 호출 이후 작업을 계속한다. 이 같은 작업 동안 제너레이터 함수는 프로그램 실행을 차단하지 않고 일시 중단된 상태로 참을성 있게 대기한다.
시간이 흐른 후 브라우저가 응답(긍정 혹은 부정)을 받을 때 프로미스 중 하나가 호출된다. 프로미스가 성공적으로 해결되면 성공 콜백이 호출되어 반복자의 next 메소드가 실행되어 제너레이터에 다른 값을 요청한다. 이는 중단되었던 제너레이터를 다시 가져와서 콜백에 의해 전달된 값을 제너레이터에 보낸다. 이것은 서보로부터 비동기적으로 가져온 ninjas 목록이 되는 값이 첫 번째 yield 표현식 이후 제너레이터의 본문을 다시 입력한다는 것을 의미한다. 제너레이터 함수의 실행은 계속되어 plan 변수에 값이 할당된다.
제너레이터 다음 줄에서 수신한 데이터 중 일부인 ninja[0].missionUrl을 사용하여 가장 유명한 닌자가 수행한 임무 목록을 포함해야 하는 다른 프로미스를 만드는 또 다른 getJSON 호출을 수행한다. 이는 비동기적 작업이어서 이 작업이 얼마나 걸릴지 모르므로 다시금 실행을 양보하고 전체 프로세스를 반복한다.
이러한 작업은 제너레이터에 비동기적 작업이 있는 한 반복된다.
이때까지 살펴본 다음과 같은 요소들을 서로 합쳐서 작성 가능하다.
- 일급 객체로서의 함수: async 함수의 인수로서 함수를 전달
- 제너레이터 함수
- 프로미스
- 콜백
- 화살표 함수
- 클로저: async 함수 내에 생성되어 제너레이터를 제어하는 반복자를 프로미스 콜백 안에서 클로저를 통해 접근
위처럼 제어 흐름과 에러 처리가 뒤섞여 복잡한 코드를 아래와 같이 간략히 작성할 수 있는 것이다.
위 예제 코드는 동기적 코드와 비동기적 코드의 장점만을 취합한 것이다. 동기적 코드를 사용하면 코드의 가독성이 높고 보편적인 모든 제어 흐름을 사용 가능하며 try-catch문 같은 예외 처리 기능 역시 사용 가능하다. 비동기적 코드의 이점이라면 작업 실행을 방해하지 않는다는 점을 들 수 있다.
요점 정리
- 제너레이터는 한꺼번에 값을 생성하는 게 아니라 각 요청마다 일련의 값들을 생성해내는 함수이다.
- 제너레이터는 일반적인 함수와 달리 실행을 중단했다가 재개한다. 값을 생성한 후 주 작업을 중단하는 일 없이 실행을 중단한 채 다음 요청을 대기한다.
- 제너레이터는 function 키워드 옆에 별표(*)를 붙여 선언한다. 제너레이터의 본문 안에 값을 생성하고 제너레이터의 실행을 중단하는 yield 키워드를 사용한다. 다른 제너레이터를 생성하고 싶다면 yield* 연산자를 활용한다.
- 제너레이터를 호출하면 제너레이터의 실행을 제어하는 반복자 객체를 생성한다. 반복자의 next 메소드를 사용함으로써 제너레이터로부터 새로운 값을 요청한다. 그리고 반복자의 throw 메소드를 호출함으로써 제너레이터에 예외를 입력할 수 있다. 이 외에도 next 메소드는 제너레이터에 값을 보내는 데도 쓰인다.
- 프로미스는 계산의 결과값을 받기 위한 플레이스홀더이다. 주로 비동기적 계산의 결과값을 받는 데 쓰인다. 프로미스는 성공 혹은 실패할 수 있고 한 번 결과가 나오면 뒤바뀌지 않는다.
- 프로미스는 비동기적 작업을 간략히 작성할 수 있다. 프로미스를 연이어 쓰기 위한 then 메소드를 사용함으로써 상호의존적이며 비동기적인 일련의 단계를 쉽게 수행할 수 있다. Promise.all 메소드를 사용하면 병렬 작업을 수행하는 비동기적 코드를 간단히 작성 가능하다.
- 제너레이터와 프로미스를 합쳐 작성하면 동기적, 비동기적 코드의 장점만을 취해 코드를 작성 가능하다.
함께 보기
- 프로미스
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promise - JavaScript | MDN
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
developer.mozilla.org
- async 함수
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
async function - JavaScript | MDN
An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly config
developer.mozilla.org
- await
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
await - JavaScript | MDN
The await operator is used to wait for a Promise. It can only be used inside an async function within regular JavaScript code; however it can be used on its own with JavaScript modules.
developer.mozilla.org
- asynchronous JavaScript
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous
Asynchronous JavaScript - Learn web development | MDN
In this module we take a look at asynchronous JavaScript, why it is important, and how it can be used to effectively handle potential blocking operations such as fetching resources from a server.
developer.mozilla.org
댓글