-
Notifications
You must be signed in to change notification settings - Fork 3
호프는 콜백함수가 정리하고 찌푼데... 🍇 #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "\uCF5C\uBC31"
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
|
|
||
| # 콜백함수와 비동기 | ||
| 먼저 해당 글은 콜백 함수와 콜백 함수의 문제점에 집중했음을 알립니다. 뒤에서 언급되는 프라미스와 Async-Await 은 간단히 등장 배경과 콜백 함수에서 어떤 문제를 해결하는지만 다루었습니다. | ||
|
|
||
| ## 목차 | ||
| 1. 콜백 함수란? | ||
| 2. 콜백 함수의 문제점 | ||
| 3. 프라미스 | ||
| 4. Async-await | ||
|
|
||
| <br/> | ||
| <br/> | ||
|
|
||
|
|
||
| ## 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 호출이 끝날 때, 중지되기 이전 위치로 다시 돌아와 나머지 후반부 프로그램이 이어집니다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ajax를 실행한 코드는 도대체 우디로 갔다가 돌아오는건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ajax는 비동기함수죠? ajax 가 스택에 쌓이면 웹 api 가 호출됩니다. 이 때 웹 api가 동작을 완료하면, 비동기 함수의 콜백함수를 callback queue에 넣습니다. |
||
| - 여기서 비결정적 시간은 A 함수의 호출이 종료되기 까지 걸리는 시간에 해당합니다. | ||
| - 즉, 콜백 함수는 프로그램의 연속성을 감싸주는(캡슐화) 장치입니다. | ||
| - 위의 함수의 실행 과정을 설명한다면 아래와 같겠죠? | ||
| ``` | ||
| A를 실행한 다음 B를 실행하고 A의 호출이 끝났을 때, C를 실행한다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 A의 호출이 끝났는데 B의 실행이 끝나지 않은 경우에는 C의 실행을 어떻게 처리하나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 동기함수인 A-B 가 오래걸린다면 C의 실행 역시 미루어질 수 밖에 없습니다. 그래서 우리 수업시간에 배운 것 중에, setTimeout 이 무조건 'n 초 후' 실행됨을 보장하지 않는다고 배웠잖아요? '최소 n초후'는 보장하지만 '무조건 n초 후'는 보장하지 않는다구요! 그것도 이와 관련된 것 같네요~ 👀 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와 호프! 답변 너무 명쾌하고 멋있어요 😎 바로 이해됐습니다 😆 |
||
| ``` | ||
|
|
||
| - 하지만 이러한 비동기 구현은 사람이 읽기에는 조금 어색하지 않나요? 이를 중점으로 콜백 함수의 문제점을 아래에서 설명하도록 합니다. | ||
|
|
||
| <br/> | ||
| <br/> | ||
|
|
||
|
|
||
| ## 2. 콜백 함수의 문제점 | ||
| ### 2-1. 순차적인 두뇌와 충돌하는 콜백함수 | ||
| 인간의 두뇌는 자바스크립트 엔진처럼 단일 스레드 방식의 이벤트 루프 큐 처럼 작동합니다. 그렇다고 우리가 할일을 처리할 때 비동기적으로 처리한다는 느낌은 받지 못합니다. 우리는 할일 목록을 만들 때 아래처럼 만들지는 않으니까요. | ||
|
|
||
| ``` | ||
| 나는 가게에 갈 거지만 도중에 분명히 전화가 올거야. 그럼 난 '안녕 엄마'라고 인사할테고 엄마가 이야기를 시작하면 난 가게 주소를 GPS로 조회하겠지. 하지만 로딩 시간은 몇 초 걸리고 난 엄마 목소리가 잘 안들려서 라디오 볼륨을 줄이는데, 그 때 불현듯 재킷을 안입고 온게 생각이 나. 밖은 얼어 죽겠는데. 그래도 일단 운전을 하면서 엄마랑 통화하는데, 안전 벨트 경고음이 울리면서 난 얘기하지 "엄마 나 지금 안전벨트 매고 있어요" 아 이제 GPS 에 약도가 나온다! | ||
|
|
||
| - You Don't know JS 중... | ||
| ``` | ||
|
|
||
| - 믿기지 않겠지만 인간의 두뇌는 위와 같이 기능을 합니다. 대신 우리는 상위 수준의 사고(계획) 에서는 이벤트 단위로 사고하지 않고, 할 일을 차례대로 계획하고, 동기적으로 계획을 실행하는 것 처럼 보입니다. | ||
|
|
||
| ``` | ||
| - 카페 가서 커피 사오기 | ||
| - 강아지 산책하기 | ||
| - 자바스크립트 공부하기 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오늘 호프의 일과였나요? 👀 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 딩동댕~~~~ |
||
| ``` | ||
| - 즉, 사람의 두뇌는 첫번째 예문처럼 기능하더라도, 두번째 처럼 계획합니다. (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 가 비동기 코드가 아니라면 또 다시 실행 순서가 변하게 됩니다. | ||
|
Comment on lines
+119
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정말 예측 불가네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그쵸 정말 엉망입니다 |
||
| - 순차적으로 이 코드를 추론하자면, 한 함수에서 다른 함수로, 또 그 다음 함수로, 시퀀스 흐름을 따라가기 위해 코드 베이스 전체를 널뛰기 해야합니다. | ||
| - 또한 만약 doA 에서 실패하면 doA 재시도는 물론, doA 다음 단계 시도역시 불가능하게 됩니다. 그러면 또 이를 처리하기 위해 에러처리를 해줘야겠죠. | ||
| - 즉, 콜백함수에서는 모든 사태와 가능한 경우의 수 (성공, 실패)를 모두 나열해야 하는 문제점이 있습니다. | ||
|
|
||
|
|
||
| ### 2-3. 믿음성의 문제 | ||
| 위에서 봤던 코드를 다시 가져옵시다. | ||
| ```javascript | ||
| // A | ||
| ajax("...", function(..){ | ||
| // C | ||
| }); | ||
| // B | ||
| ``` | ||
|
|
||
| - A와 B는 자바스크립트 메인 프로그램의 제어를 직접 받으며 ‘지금’ 실행되지만, C는 다른 프로그램 (여기서는 ajax)의 제어하에 ‘나중’에 실행됩니다. | ||
| - 콜백 함수 C 를 넘겨준 ajax 는 우리 개발자가 직접 제어할 수 있는 함수가 아니라, 서드 파티가 제공한 유틸리티인 경우가 대부분입니다. | ||
| - 즉, 내가 작성하는 프로그램인데도 불구하고 실행 흐름을 서드 파티에 의존해야 하는 현상이 생깁니다. `이를 제어의 역전(Inversion of Control) 이라고 합니다.` | ||
| - 제어의 역전이 생기면 개발자는 서드파티로 전달된 콜백 함수가 그저 잘 실행되기만을 기도하는 수 밖에 없습니다. | ||
| - 만약 서드 파티 내부의 로직이 어느날 갑자기 변경되면 어떨까요? 우리가 작성한 함수를 전부 수정해야하는 문제점이 생길 수도 있습니다. | ||
|
|
||
|
|
||
|
|
||
| 그러면 이러한 콜백함수의 문제점을 해결하기 위해 등장한 새로운 문법들을 소개합니다. | ||
|
|
||
| <br/> | ||
| <br/> | ||
|
|
||
| ## 3. 프라미스 | ||
| > 프라미스에 대한 자세한 설명은 나중에 개별 포스팅을 통해 하겠습니다. 여기서는 왜 콜백함수의 대안으로 프라미스가 나왔는지 설명할게요. | ||
|
|
||
| - 위에서 콜백 함수의 가장 큰 문제점인 `제어의 역전`을 봤습니다. 만일 제어의 역전을 되역전 시킬 수 있다면 어떨까요? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !!제어 -> 제어가 boolean화 되겠네요 👀 |
||
| - 즉, 프로그램의 진행을 다른 파트에 넘겨주지 않고도 개발자가 언제 작업이 끝날지 알 수 있고, 그 다음에 무슨일을 해야할 지 스스로 결정할 수 있다면 어떨까요? | ||
| - 이를 위해 고안된 방법이 바로 프라미스입니다. | ||
| - 그러면 간단하게 프라미스가 어떻게 제어의 역전의 문제들을 해결하는지 하나씩 살펴봅시다. | ||
|
|
||
|
|
||
| ### 제어의 역전 문제 | ||
| - 너무 일찍 콜백을 호출 할 수 있다. | ||
| - 너무 늦게 콜백을 호출 할 수 있다. (혹은 전혀 호출하지 않을 수 있다.) | ||
| - 너무 적게, 아니면 너무 많이 콜백을 호출 할 수 있다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일찍, 늦게, 적게, 많이는 사람에게는 서로 기준이 다른 단위인데, 혹시 자바스크립트에게는 js만의 콜백에 대한 기준이 있는걸까요? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거는 콜백을 외부의 고차함수에 넣은 개발자의 입장에서 책에서 설명해주는 겁니다! 딱히 정확한 기준은 없습니다. 설명하자면 아래와 같아요.
|
||
| - 필요한 환경/인자를 정상적으로 콜백에 전달하지 못할 수 있다. | ||
| - 발생 가능한 에러/예외를 무시 할 수 있다. | ||
|
|
||
|
|
||
| ### 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 | ||
|
Comment on lines
+181
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위 코드의 경우 마이크로태스크 큐에 어떤 순서로 들어가는건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 아래와 같이 생각했는데 민초는 어떻게 생각하시는지 알려주세요~ p.then(function (){ // 1번
p.then(function(){
console.log("C"); // 3번
})
console.log("A");
})
p.then(function(){ // 2번
console.log("B")
})1->2->3 순서로 들어간다고 생각했는데요, 그 이유는 |
||
| ``` | ||
|
|
||
| - 위의 코드를 보면 프라미스의 작동원리 (then 에 등록된 콜백들이 그 다음 비동기 기회가 찾아왔을 때 순서대로 실행)에 따라서 “C” 가 갑자기 툭 끼어들어 “B”를 앞지를 일이 없습니다. | ||
|
|
||
| ### 3-3. 한번도 콜백을 호출하지 않는 문제 해결방법 | ||
| - 프라미스 스스로 귀결된 후, 귀결 사실을 알리지 못하게 막을 방도가 없습니다. | ||
| - fullfiled/rejected 콜백이 프라미스에 모두 등록된 상태라면, 프라미스 귀결 시 둘중 하나는 반드시 호출됩니다. | ||
|
|
||
|
|
||
| ### 3-4. 너무 가끔, 너무 종종 호출하는 경우 해결방법 | ||
| - 콜백의 호출 횟수는 당연히 ‘한 번’ 입니다. | ||
| - ‘너무 종종’ 호출 하는 경우는 쉽게 해결됩니다. 프라미스는 정의상 단 한번만 귀결됩니다. 어떤 이유로 프라미스 생성코드가 resolve(), reject() 둘 중 하나 또는 모두를 여러 차례 호출 하려고 하면 프라미스는 오직 최초의 귀결만 호출한 후 이후의 시도를 무시합니다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여러 차례 호출해도 최초의 귀결만 호출한다는게 어떤 의미인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. const myPromise = new Promise((resolve, reject) => {
resolve('foo');
resolve('foo1');
resolve('foo2');
resolve('foo3');
});위와 같은 경우를 의미합니다. 이럴 경우 최초의 resolve 값만 호출하고 나머지는 무시하거든요~ |
||
|
|
||
|
|
||
| ### 3-5. 인자/ 환경 실패 경우 해결방법 | ||
| - 프라미스 귀결 값은 딱 하나 뿐이다. 명시적 값으로 귀결되지 않으면 그 값은 undefined 로 세팅됩니다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로미스 귀결값이라는게 resolve, reject를 통해 전달되는 값을 말하는건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 맞습니다 |
||
| - 하지만 지금이든, 나중이든 프라미스는 등록한 콜백으로 반드시 전해집니다. | ||
|
|
||
|
|
||
| ### 3-6. 에러/예외를 무시하는 경우 해결방법 | ||
| - 어떤 이유로 프라미스를 버리게 되면, 그 값은 반드시 reject 콜백으로 전달됩니다. | ||
|
|
||
|
|
||
| ### 결론 | ||
| - 결국 프라미스는 콜백을 완전히 없애기 위한 장치는 아닙니다. 단지 프라미스는 콜백을 처리하는 위치를 다르게 할 뿐입니다. | ||
| - 고차함수에 콜백을 바로 넘기지 않고 고차함수에서 뭔가(프라미스 객체)를 반환 받아 이 ‘뭔가’ 에 콜백을 전달해줍니다. | ||
|
|
||
| <br/> | ||
| <br/> | ||
|
|
||
| ## 4. Async-await | ||
| 위에서 프라미스를 통해 콜백함수 패턴의 문제점인 ‘제어의 역전’을 해결할 수 있었습니다. 하지만 Promise 패턴 역시 우리 인간이 읽기에는 충분히 동기적이지 않습니다. 이를 해결 하기 위해 등장한 문법이 바로 async-await 입니다. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| > async-await 은 사실 제너레이터로 구현되어있습니다. 이는 추후에 다른 포스팅을 통해 더 자세히 살펴보도록 합니다. | ||
|
|
||
| - 우리는 async-await 패턴을 사용하여 위에서 살펴본 프라미스의 후속 처리 메서드 (then,catch,finally) 없이 동기적인 프로그램 처럼 비동기 값을 반환 받고 처리할 수 있습니다. | ||
|
|
||
|
|
||
| <br/> | ||
| <br/> | ||
|
|
||
|
|
||
| ## 참고 | ||
| - You Don’t know JS | ||
| - 코어 자바스크립트 | ||
| - 모던 자바스크립트 딥다이브 | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무조건 콜백 함수를 실행하는데에 조건이 필요한가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그건 콜백함수를 인자로 받는 고차함수에 따라 달라질 것 같아요.
다만 콜백함수를 인자로 넣는 개발자 입장에서는 당연히 '어느 조건에 실행해줘~'라는 의도로 콜백함수를 인자로 전달하지 않을까요? 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 그렇군요!