From c6bf08a1eae8994a91f80dc61d4a8dad59310e81 Mon Sep 17 00:00:00 2001 From: moonheekim0118 Date: Tue, 12 Apr 2022 20:30:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=BD=9C=EB=B0=B1=ED=95=A8=EC=88=98=20=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...34\353\260\261\355\225\250\354\210\230.md" | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 "\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" diff --git "a/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" "b/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" new file mode 100644 index 0000000..5670d77 --- /dev/null +++ "b/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" @@ -0,0 +1,238 @@ + +# 콜백함수와 비동기 +먼저 해당 글은 콜백 함수와 콜백 함수의 문제점에 집중했음을 알립니다. 뒤에서 언급되는 프라미스와 Async-Await 은 간단히 등장 배경과 콜백 함수에서 어떤 문제를 해결하는지만 다루었습니다. + +## 목차 +1. 콜백 함수란? +2. 콜백 함수의 문제점 +3. 프라미스 +4. Async-await + +
+
+ + +## 1. 콜백 함수란? +- callback 은 ‘부르다’ ‘호출하다’라는 의미인, call 과, ‘뒤돌아오다’, ‘되돌다’라는 의미인 back 의 합성어로, **되돌아 호출해달라**는 명령어 입니다. +- 즉, 어떤 함수 X 를 호출 하면서 , 특정 조건일 때 함수 Y를 실행해달라는 의미입니다. +- 콜백 함수는 다른 코드에게 인자로 함수로 넘겨줌으로써, 그 제어권을 함께 위임한 함수를 말합니다. +- 이 때, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수 (Higher-Order-Function)이라고 하고, 매개변수를 통해 다른 하뭇의 내부로 전달되는 함수를 콜백 함수(Callback Function)이라고 합니다. +- 고차 함수는 콜백 함수를 자신의 일부분으로 합성하여, 매개변수를 통해 전달받은 함수의 호출 시점을 결정하여 호출합니다. + +### 그러면 콜백 함수가 왜 필요할까요? + +#### 1. 함수를 합성하여, 반복을 줄입니다. +아래에 어떤 일을 반복 수행하는 repeat 함수를 정의해봅시다. +```javascript +function repeat(n){ + for(let i = 0 ; i < n ; i ++) console.log(i); +} +``` + +- 위의 함수는 매개변수로 받은 n 만큼 console.log 를 호출합니다. +- 그런데 만약 우리가 repeat 함수 내부 반복문에서 다른 일을 하고 싶다면 어떻게 해야할까요? 그 때 마다 매번 repeat 함수를 재정의 해야할까요? +- 콜백함수는 이러한 문제점을 해결해주기도 합니다. 즉 **함수를 합성**하여 함수의 변하지 않는 공통 로직은 미리 정의해 두고, 경우에 따라 변경되는 로직은 추상화하여 함수 외부에서 내부로 전달하는 것입니다. + +```javascript +function repeat(n, callback){ + for(let i = 0 ; i < n ; i ++) callback(i); +} + +const logAll = function(i){ + console.log(i); +} + +const logOdds = function(i){ + if(i%2!==0) console.log(i); +} + +repeat(10, logAll); +repeat(10, logOdds); +``` + + +- 위의 repeat 함수는 경우에 따라 변경되는 일을 함수 callback 으로 추상화하여, 외부에서 전달받게 됩니다. 따라서 repeat 함수는 더 이상 내부 로직에 강력히 의존하지 않고, 외부에서 로직의 일부분을 함수로 전달받아 수행하여 더욱 유연하게 됩니다. + + +#### 2. 비동기를 구현해줍니다. +```javascript +// A +ajax("..", function(..){ + // C +}); +// B +``` +- A 와 B 는 프로그램의 전반부 (“지금”) 에 해당하고, ajax 함수의 콜백함수 C 는 후반부 (“나중”)에 해당합니다. +- 전반부 코드가 곧장 실행되면 비결정적 시간 동안 중지되고, 언젠가 ajax 호출이 끝날 때, 중지되기 이전 위치로 다시 돌아와 나머지 후반부 프로그램이 이어집니다. + - 여기서 비결정적 시간은 A 함수의 호출이 종료되기 까지 걸리는 시간에 해당합니다. +- 즉, 콜백 함수는 프로그램의 연속성을 감싸주는(캡슐화) 장치입니다. +- 위의 함수의 실행 과정을 설명한다면 아래와 같겠죠? +``` +A를 실행한 다음 B를 실행하고 A의 호출이 끝났을 때, C를 실행한다. +``` + +- 하지만 이러한 비동기 구현은 사람이 읽기에는 조금 어색하지 않나요? 이를 중점으로 콜백 함수의 문제점을 아래에서 설명하도록 합니다. + +
+
+ + +## 2. 콜백 함수의 문제점 +### 2-1. 순차적인 두뇌와 충돌하는 콜백함수 +인간의 두뇌는 자바스크립트 엔진처럼 단일 스레드 방식의 이벤트 루프 큐 처럼 작동합니다. 그렇다고 우리가 할일을 처리할 때 비동기적으로 처리한다는 느낌은 받지 못합니다. 우리는 할일 목록을 만들 때 아래처럼 만들지는 않으니까요. + +``` +나는 가게에 갈 거지만 도중에 분명히 전화가 올거야. 그럼 난 '안녕 엄마'라고 인사할테고 엄마가 이야기를 시작하면 난 가게 주소를 GPS로 조회하겠지. 하지만 로딩 시간은 몇 초 걸리고 난 엄마 목소리가 잘 안들려서 라디오 볼륨을 줄이는데, 그 때 불현듯 재킷을 안입고 온게 생각이 나. 밖은 얼어 죽겠는데. 그래도 일단 운전을 하면서 엄마랑 통화하는데, 안전 벨트 경고음이 울리면서 난 애기하지 "엄마 나 지금 안전벨트 매고 있어요" 아 이제 GPS 에 약도가 나온다! + +- You Don't know JS 중... +``` + +- 믿기지 않겠지만 인간의 두뇌는 위와 같이 기능을 합니다. 대신 우리는 상위 수준의 사고(계획) 에서는 이벤트 단위로 사고하지 않고, 할 일을 차례대로 계획하고, 동기적으로 계획을 실행하는 것 처럼 보입니다. + +``` +- 카페 가서 커피 사오기 +- 강아지 산책하기 +- 자바스크립트 공부하기 +``` +- 즉, 사람의 두뇌는 첫번째 예문처럼 기능하더라도, 두번째 처럼 계획합니다. (A다음 ,B 다음 , C…) +- 따라서 콜백으로 비동기성을 표현하는 방식이 동기적인 두뇌의 사고 흐름과 맞지 않게됩니다. 인간은 단계별 (step-by-step)로 끊어 생각하는 경향이 있기 때문이죠. +- 그러므로 콜백으로 비동기 자바스크립트 코드를 정확하게 작성하고 추론하기란 쉽지 않습니다. 애초의 인간의 두뇌가 그렇게 작동하지 않으니까요! + +### 2-2. 콜백 지옥 +- 콜백지옥의 문제점은 대부분 단순히 ‘중첩이 심하다’ ‘들여쓰기가 심하다’정도로 알고 있을 가능성이 높습니다. 하지만, 콜백 지옥은 단순히 그 이상의 문제점을 가집니다. + +```javascript +doA(function(){ + doB(); + + doC(function (){ + doD(); + }) + + doE(); +}); + +doF(); +``` + +- 위 코드의 실행 순서는 아래와 같습니다. +1. doA() +2. doF() +3. doB() +4. doC() +5. doE() +6. doD() +- 그런데 만약 doA 나 doC 가 비동기 코드가 아니라면 또 다시 실행 순서가 변하게 됩니다. +- 순차적으로 이 코드를 추론하자면, 한 함수에서 다른 함수로, 또 그 다음 함수로, 시퀀스 흐름을 따라가기 위해 코드 베이스 전체를 널뛰기 해야합니다. +- 또한 만약 doA 에서 실패하면 doA 재시도는 물론, doA 다음 단계 시도역시 불가능하게 됩니다. 그러면 또 이를 처리하기 위해 에러처리를 해줘야겠죠. +- 즉, 콜백함수에서는 모든 사태와 가능한 경우의 수 (성공, 실패)를 모두 나열해야 하는 문제점이 있습니다. + + +### 2-3. 믿음성의 문제 +위에서 봤던 코드를 다시 가져옵시다. +```javascript +// A +ajax("...", function(..){ + // C +}); +// B +``` + +- A와 B는 자바스크립트 메인 프로그램의 제어를 직접 받으며 ‘지금’ 실행되지만, C는 다른 프로그램 (여기서는 ajax)의 제어하에 ‘나중’에 실행됩니다. +- 콜백 함수 C 를 넘겨준 ajax 는 우리 개발자가 직접 제어할 수 있는 함수가 아니라, 서드 파티가 제공한 유틸리티인 경우가 대부분입니다. +- 즉, 내가 작성하는 프로그램인데도 불구하고 실행 흐름을 서드 파티에 의존해야 하는 현상이 생깁니다. `이를 제어의 역전(Inversion of Control) 이라고 합니다.` +- 제어의 역전이 생기면 개발자는 서드파티로 전달된 콜백 함수가 그저 잘 실행되기만을 기도하는 수 밖에 없습니다. +- 만약 서드 파티 내부의 로직이 어느날 갑자기 변경되면 어떨까요? 우리가 작성한 함수를 전부 수정해야하는 문제점이 생길 수도 있습니다. + + + +그러면 이러한 콜백함수의 문제점을 해결하기 위해 등장한 새로운 문법들을 소개합니다. + +
+
+ +## 3. 프라미스 +> 프라미스에 대한 자세한 설명은 나중에 개별 포스팅을 통해 하겠습니다. 여기서는 왜 콜백함수의 대안으로 프라미스가 나왔는지 설명할게요. + +- 위에서 콜백 함수의 가장 큰 문제점인 `제어의 역전`을 봤습니다. 만일 제어의 역전을 되역전 시킬 수 있다면 어떨까요? +- 즉, 프로그램의 진행을 다른 파트에 넘겨주지 않고도 개발자가 언제 작업이 끝날지 알 수 있고, 그 다음에 무슨일을 해야할 지 스스로 결정할 수 있다면 어떨까요? +- 이를 위해 고안된 방법이 바로 프라미스입니다. +- 그러면 간단하게 프라미스가 어떻게 제어의 역전의 문제들을 해결하는지 하나씩 살펴봅시다. + + +### 제어의 역전 문제 +- 너무 일찍 콜백을 호출 할 수 있다. +- 너무 늦게 콜백을 호출 할 수 있다. (혹은 전혀 호출하지 않을 수 있다.) +- 너무 적게, 아니면 너무 많이 콜백을 호출 할 수 있다. +- 필요한 환경/인자를 정상적으로 콜백에 전달하지 못할 수 있다. +- 발생 가능한 에러/예외를 무시 할 수 있다. + + +### 3-1. 너무 일찍 콜백을 호출 해결방법 +- 콜백 패턴을 사용하면, 고차함수가 어떨 때는 동기적으로, 어떨 때는 비동기적으로 끝나는 문제가 생길 수 있습니다. (우리는 고차함수의 내부 로직을 알수 없으니까요!) +- 프라미스에서 then() 을 호출하면 프라미스가 이미 귀결된 이후라 해도, then () 에 건넨 콜백은 항상 비동기적으로만 호출됩니다. + + +### 3-2. 너무 늦게 콜백을 호출 해결방법 +- 프라미스 then() 에 등록한 콜백은 새 프라미스가 생성되면서 `resolve(), reject()` 중 어느 한쪽은 자동으로 호출하도록 스케쥴링 되어있습니다. +- 이렇게 스케쥴링된 두 콜백은 다음 비동기 시점에 예상대로 실행됩니다. +- 즉, 프라미스가 귀결되면 then() 에 등록된 콜백들이 순서대로 실행되며, 한 콜백 내부에서 다른 콜백의 호출에 영향을 주거나 지연 시킬 수는 없습니다. + +```javascript +p.then(function (){ + p.then(function(){ + console.log("C"); + }) + console.log("A"); +}) + +p.then(function(){ + console.log("B") +}) + +// A B C +``` + +- 위의 코드를 보면 프라미스의 작동원리 (then 에 등록된 콜백들이 그 다음 비동기 기회가 찾아왔을 때 순서대로 실행)에 따라서 “C” 가 갑자기 툭 끼어들어 “B”를 앞지를 일이 없습니다. + +### 3-3. 한번도 콜백을 호출하지 않는 문제 해결방법 +- 프라미스 스스로 귀결된 후, 귀결 사실을 알리지 못하게 막을 방도가 없습니다. +- fullfiled/rejected 콜백이 프라미스에 모두 등록된 상태라면, 프라미스 귀결 시 둘중 하나는 반드시 호출됩니다. + + +### 3-4. 너무 가끔, 너무 종종 호출하는 경우 해결방법 +- 콜백의 호출 횟수는 당연히 ‘한 번’ 입니다. +- ‘너무 종종’ 호출 하는 경우는 쉽게 해결됩니다. 프라미스는 정의상 단 한번만 귀결됩니다. 어떤 이유로 프라미스 생성코드가 resolve(), reject() 둘 중 하나 또는 모두를 여러 차례 호출 하려고 하면 프라미스는 오직 최초의 귀결만 호출한 후 이후의 시도를 무시합니다. + + +### 3-5. 인자/ 환경 실패 경우 해결방법 +- 프라미스 귀결 값은 딱 하나 뿐이다. 명시적 값으로 귀결되지 않으면 그 값은 undefined 로 세팅됩니다. +- 하지만 지금이든, 나중이든 프라미스는 등록한 콜백으로 반드시 전해집니다. + + +### 3-6. 에러/예외를 무시하는 경우 해결방법 +- 어떤 이유로 프라미스를 버리게 되면, 그 값은 반다시 reject 콜백으로 전달됩니다. + + +### 결론 +- 결국 프라미스는 콜백을 완전히 없애기 위한 장치는 아닙니다. 단지 프라미스는 콜백을 처리하는 위치를 다르게 할 뿐입니다. +- 고차함수에 콜백을 바로 넘기지 않고 고차함수에서 뭔가(프라미스 객체)를 반환 받아 이 ‘뭔가’ 에 콜백을 전달해줍니다. + +
+
+ +## 4. Async-await +위에서 프라미스를 통해 콜백함수 패턴의 문제점인 ‘제어의 역전’을 해결할 수 있었습니다. 하지만 Promise 패턴 역시 우리 인간이 읽기에는 충분히 동기적이지 않습니다. 이를 해결 하기 위해 등장한 문법이 바로 async-await 입니다. +> async-await 은 사실 제너레이터로 구현되어있습니다. 이는 추후에 다른 포스팅을 통해 더 자세히 살펴보도록 합니다. + +- 우리는 async-await 패턴을 사용하여 위에서 살펴본 프라미스의 후속 처리 메서드 (then,catch,finally) 없이 동기적인 프로그램 처럼 비동기 값을 반환 받고 처리할 수 있습니다. + + +
+
+ + +## 참고 +- You Don’t know JS +- 코어 자바스크립트 +- 모던 자바스크립트 딥다이브 From 561dc478c7ecf800df714ccd7622ecab8c0d596f Mon Sep 17 00:00:00 2001 From: moonheekim0118 Date: Wed, 13 Apr 2022 04:18:59 +0900 Subject: [PATCH 2/2] Fix typo --- .../04-\354\275\234\353\260\261\355\225\250\354\210\230.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" "b/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" index 5670d77..ffbb557 100644 --- "a/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" +++ "b/\355\230\270\355\224\204/04-\354\275\234\353\260\261\355\225\250\354\210\230.md" @@ -16,7 +16,7 @@ - callback 은 ‘부르다’ ‘호출하다’라는 의미인, call 과, ‘뒤돌아오다’, ‘되돌다’라는 의미인 back 의 합성어로, **되돌아 호출해달라**는 명령어 입니다. - 즉, 어떤 함수 X 를 호출 하면서 , 특정 조건일 때 함수 Y를 실행해달라는 의미입니다. - 콜백 함수는 다른 코드에게 인자로 함수로 넘겨줌으로써, 그 제어권을 함께 위임한 함수를 말합니다. -- 이 때, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수 (Higher-Order-Function)이라고 하고, 매개변수를 통해 다른 하뭇의 내부로 전달되는 함수를 콜백 함수(Callback Function)이라고 합니다. +- 이 때, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수 (Higher-Order-Function)이라고 하고, 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(Callback Function)이라고 합니다. - 고차 함수는 콜백 함수를 자신의 일부분으로 합성하여, 매개변수를 통해 전달받은 함수의 호출 시점을 결정하여 호출합니다. ### 그러면 콜백 함수가 왜 필요할까요? @@ -82,7 +82,7 @@ A를 실행한 다음 B를 실행하고 A의 호출이 끝났을 때, C를 실 인간의 두뇌는 자바스크립트 엔진처럼 단일 스레드 방식의 이벤트 루프 큐 처럼 작동합니다. 그렇다고 우리가 할일을 처리할 때 비동기적으로 처리한다는 느낌은 받지 못합니다. 우리는 할일 목록을 만들 때 아래처럼 만들지는 않으니까요. ``` -나는 가게에 갈 거지만 도중에 분명히 전화가 올거야. 그럼 난 '안녕 엄마'라고 인사할테고 엄마가 이야기를 시작하면 난 가게 주소를 GPS로 조회하겠지. 하지만 로딩 시간은 몇 초 걸리고 난 엄마 목소리가 잘 안들려서 라디오 볼륨을 줄이는데, 그 때 불현듯 재킷을 안입고 온게 생각이 나. 밖은 얼어 죽겠는데. 그래도 일단 운전을 하면서 엄마랑 통화하는데, 안전 벨트 경고음이 울리면서 난 애기하지 "엄마 나 지금 안전벨트 매고 있어요" 아 이제 GPS 에 약도가 나온다! +나는 가게에 갈 거지만 도중에 분명히 전화가 올거야. 그럼 난 '안녕 엄마'라고 인사할테고 엄마가 이야기를 시작하면 난 가게 주소를 GPS로 조회하겠지. 하지만 로딩 시간은 몇 초 걸리고 난 엄마 목소리가 잘 안들려서 라디오 볼륨을 줄이는데, 그 때 불현듯 재킷을 안입고 온게 생각이 나. 밖은 얼어 죽겠는데. 그래도 일단 운전을 하면서 엄마랑 통화하는데, 안전 벨트 경고음이 울리면서 난 얘기하지 "엄마 나 지금 안전벨트 매고 있어요" 아 이제 GPS 에 약도가 나온다! - You Don't know JS 중... ``` @@ -211,7 +211,7 @@ p.then(function(){ ### 3-6. 에러/예외를 무시하는 경우 해결방법 -- 어떤 이유로 프라미스를 버리게 되면, 그 값은 반다시 reject 콜백으로 전달됩니다. +- 어떤 이유로 프라미스를 버리게 되면, 그 값은 반드시 reject 콜백으로 전달됩니다. ### 결론