2023. 10. 19. 20:43ㆍJS
아 생각을 잘 못 한 것 같다
사전에 진도 쫙 빼놓고 프로젝트 몇개하고
캠프 와야하는데 그게 아니라 맨땅에 헤딩한다고 0 부터 하려고 하니
이것저것 하느라 정리는 뒷전이고 진도 빼기 바쁘다
이해 절반도 못 한 것 같은데 일단 올리고
개인 과제 좀 하면서 좀 더 쉬운 강의로 다지고 난 후 다시 봐야겠다
PART17. 비동기 javaScript: 프로미스 & 콜백
349. 동기 코드 실행 이해하기 (“Sync Code”)
동기 코드와 JavaScript의 작동 방식을 이해하는 데서부터 시작해 보죠
본 코스를 시작할 때에
제가 JavaScript는 단일 스레드라고 짚은 바 있습니다
지금까지는 그 말의 의미가 크게 중요하지 않았습니다만
단일 스레드라는 말은
JavaScript는 한 번에 하나의 작업만 수행할 수 있다는 뜻입니다
따라서 여러 코드가 있을 때
하나를 console.log 처리하고 이후 함수를 호출한 뒤
해당 함수나 다른 곳에서 함수가 실행을 마친 뒤
button.disabled를 확인하고 또 다른 함수를 실행하는 겁니다
여기서 중요한 점은 이 모든 작업이
하나가 끝난 뒤 순차적으로 실행되고 있으며
동시에 실행되는 작업이 아니라는 점입니다
350. 비동기 코드 실행 이해하기 (“Async Code”)
스크립트가 차단된다는 의미는 다음 줄이 실행되지 못한다는 것이 아니라
다른 어떤 코드도 실행되지 못한다는 겁니다
다행스럽게도 JavaScript와 브라우저가 해결 방법을 제시합니다
비동기 코드 실행이죠 이 setTimeout 타이머 예시를
다시 볼 텐데 여기서 정말 중요한 건
HTTP 요청을 다룰 때에도 동일하게 적용된다는 겁니다
따라서 일반적으로 더 오랜 시간이 소요되는 작업을 할 경우
해당 작업을 브라우저에서 오프로드(Off-load) 시킬 수 있습니다
이제 이 연산을 브라우저에서 오프로드 시키는 것인데
setTimeout를 호출하여 브라우저에 타이머를 설정합니다
브라우저가 해당 작업을 완료하면 다음 코드가
바로 실행될 수 있습니다 사실상 브라우저가
다중 스레드를 사용할 수 있게 된 거죠 하나는 JavaScript
또 다른 하나는 다음 태스크에 대해서 말입니다
즉 타이머는 실행 중인 JavaScript 코드에서 분리되어
브라우저에서 관리됩니다 이렇게 하면 해당
JavaScript 코드도 차단되지 않고 브라우저도 타이머를
관리할 수 있는 거죠, 응답을 기다리는 HTTP 요청의 경우에도 마찬가지인데요
위치 정보를 기다리는 사용자 위치 경우에 대해서도 마찬가지죠
브라우저가 이와 같은 태스크를 처리하고 다중 스레드로 관리함으로써
JavaScript 코드에 대한 차단도 일어나지 않는 겁니다
단 이때 브라우저가 JavaScript 코드와
의사소통할 수단이 필요합니다
대게 그 방법으로 콜백 함수를 사용하죠
기억하시다시피 setTimeout에
콜백 함수를 첫 번째 인자로 입력해 줄 겁니다
이렇게 하면 콜백 함수가 브라우저에서
연산이 끝나면 호출할 함수가 되는 거죠
브라우저가 다시 스크립트로 돌아가서
그 안에 있는 작업을 수행합니다 이렇게 되면 스크립트가
실행도 계속 이어나갈 수 있으며 시간이 더 오래 소요되는
연산이 끝난 뒤에도 실행이 계속 이어지죠
351. 코드 차단하기 & “이벤트 반복문”-pdf파일 확인하는 게 나음
이벤트 루프는 메시지 대기열과 같이 브라우저의 빌트인 기능입니다
그리고 대부분의 JavaScript 환경, 예를 들자면
Node.js에도 이벤트 루프라는 개념이 존재합니다
이 이벤트 루프는 JavaScript 엔진이 아니라 JavaScript의
호스트 환경 중 일부라는 점을 제대로 이해하셔야 합니다
JavaScript 엔진을 사용하는 항목 중 하나일 뿐이죠
즉 이벤트 루프는 브라우저의 일부이며
이벤트 루프의 역할은 엔진의 호출 스택을
대기 중인 메시지와 동기화하는 데에 있습니다
결국에 이벤트 루프는 항시 실행 중인 상태로
스택이 비어 있는 걸 보면
대기 중인 작업이 있는지 확인하고 비어 있을 때에 이벤트 루프가 실행되어서
대기 중인 메시지나 작업 대상인 함수를
호출 스택으로 푸시하는 겁니다
따라서 이벤트 루프는 호출 스택이 빈 상태가
될 때까지 기다렸다가 메시지 또는 앞서 등록한
콜백 함수를 이동시켜서
호출 스택을 통해 실행될 수 있도록 합니다
352. 동기화 + 비동기화 코드 - 실행 순서 <–강의 다시 보는게 나음
function trackUserHandler() {
navigator.geolocation.getCurrentPosition(
posData =>{
console.log(posData);
},
error => {
console.log(error);
}
);
}
trackUserHandler가 있으니
이를 통해서 Clicked를 로그하지 않고
사용자 위치를 얻도록 설정해 보겠습니다
이제 navigator 객체에 geolocation 메서드를 이용하는데
이는 빌트인 API로 브라우저에 액세스하여
getCurrentPosition으로 사용자 위치를 가져옵니다
getCurrentPosition는 메서드의 하나로
해당하는 매개변수를 살펴보면 세 가지 잠재적 매개 변수가
존재함을 알 수 있습니다 successCallback
즉 위치를 성공적으로 가져온 경우에 실행할 함수와
errorCallback 즉 위치를 성공적으로
가져오지 못했을 경우 실행할 함수
그리고 마지막으로는 options 객체로 이를 통해서는
위치를 가져오는 방법을 구성합니다 그리고 따로 name 함수를
설정해서 이름을 입력할 수도 있습니다
이렇게 저장한 다음 페이지를 새로 고침해서
Track Me 버튼을 눌러 보면 액세스를 허용할지에 대한
메시지가 뜹니다 Allow를 클릭하면
내 위치를 확인하고 해당 위치 정보를 표시해 주죠
잠깐 시간이 지난 뒤 실행이 끝나면 결과를
살펴볼 수 있습니다 좌표가 나오는데 이 좌표를
읽어 들여 앱에서 필요한 작업을 수행하면 되죠
여러 앱에서 편하게 사용할 수 있는데
예를 들어 음식 배달 웹 서비스를 구축할 때에
사용자 위치 정보를 알면 아주 유용하게 쓸 수 있겠죠
353. 다수의 콜백 & setTimeout(0)
콜백을 더 중첩할 수록
코드는 읽기 더 힘들어지고 유지하기 힘들어집니다
코드를 저장하고 새로고침해서 Track Me!를 클릭한 후
Allow를 클릭하면 Position을 가져오는 데에 시간이 좀 걸리고
타이머 때문에 추가로 2초가 걸립니다
그 시간이 지나야 이 객체를 띄웁니다
여기서 알려드리고자 하는 것은 이러한 작업들을
중첩할 수 있다는 겁니다 비동기 연산 안에
비동기 연산을 포함할 수도 있습니다
콜백 함수가 실행되는 최소 시간이지
보장된 시간이 아니라는 겁니다
354. 프로미스 시작하기
다른 콜백과 함께 setTimeout을 사용하는 콜백이 있고
또 다른 콜백을 사용하는 다른 함수가 있다고 해봅시다
그러면 이른바 "콜백 지옥"에 이르게 됩니다
읽기도 유지하기도 까다롭고 중첩된 것들이
무엇인지 어디에 중첩된 건지도 어떤 변수가 어디에
접근할 수 있는지도 추적하기가 어렵죠 이 코드는 보기 좋은 코드가 아닙니다
다행히도 JavaScript에는 이에 대한 솔루션이 있는데
그것이 바로 프로미스입니다
Promise는 다음과 같이 더 깨끗한 코드를 작성할 수 있습니다
작업이 몇 가지 있고 특수한 then 키워드를 사용하여
then에 콜백을 전달합니다
하지만 만일 첫 번째 작업에 의존하는 다른 작업이 있으면
서로 중첩시키는 대신 또 하나의 then 블록을 추가합니다
여기에서는 여러 개의 중첩 대신
하나의 중첩만을 사용하여 코드를 더 읽기 쉽게 해줍니다
불행히도 setTimeout과 getCurrentPosition 프로미스 버전이 없어서
then을 추가하여 해당 프로미스를 사용하여 then에 접근할 수 없습니다
setTimeout 같은 오래된 기능들은 여전히 콜백을 씁니다 이제는 이것들을
프로미스 지원 코드로 감쌀 수 있고 이 강의에서 프로미스가 내부적으로
어떻게 작동하는지도 배울 겁니다
프로미스는 생성자 함수 또는 JavaScript에 내장된 클래스라서
const promise = new promise((reslove, reject) => {
});
이렇게 생성할 수 있습니다 여기 이 프로미스가
함수를 인자로 받고
즉, 생성자가 함수를 받는데 이 함수는 프로미스 API라서
이 프로미스가 생성될 때 바로 실행됩니다
여기서 프로미스를 구축할 때
이 생성자에 전달하는 함수가 바로 실행됩니다
즉, 생성자 내부에서 호출되는 거죠
이것이 프로미스가 무엇을 해야 하는지
무엇을 감싸야 하는지를 구성하는 방법입니다
이 함수는 두 개의 인자를 생성자에
전달합니다, 그러니까 여기 이 함수가
두 개의 인자를 받고 각 인자는 그 자체가
함수가 됩니다 resolve 함수와
reject 함수로 보통 이름 짓지만
이 인자는 여러분이 원하시는 대로
여러분의 코드에 반영하면 됩니다
이제 함수 본문에서는
프로미스가 생성되는 즉시 실행됩니다
여기 함수 본문에서 어떤 일이 일어나야 하는지 정의할 수 있습니다
원하신다면 resolve에
인자를 전달할 수 있는데요, 가령 텍스트가 될 수도 있겠지만
객체, 배열, 숫자, 원하는 것은 모두 넣을 수 있습니다
const promise = new promise( (resovle, reject) => {
setTimer(() => {
resovle();
}, duration);
이걸 종종 내장 API를 프로미스화한다고 부릅니다
355. 다수의 프로미스 체이닝(Chaining) <- 뭔 말인지 못 알아먹음 다시 듣기
자, 이제 프로미스 체이닝이라는 걸 볼 거예요
이 프로미스에는 여러 단계가 있습니다
첫 번째 프로미스가 끝날 때까지 기다렸다가 여기에서 무언가를
반환합니다, 꼭 프로미스여야 할
필요는 없습니다 문자열 같은 다른 데이터였다면
프로미스로 자동으로 감싸질 겁니다
그러면 프로미스가 아닌 데이터를 이 프로미스가
해결할 때까지 기다립니다 물론 즉시 해결될 겁니다
프로미스를 전달하게 되면 완료될 때까지 기다리게 되는데
이 경우 타이머가 만료되면 2초로 설정해 둡시다
프로미스가 완료되면 다음 then 단계로 가서
반환된 프로미스의 데이터로 이를 실행하거나
데이터를 이렇게 반환하게 되면
이는 프로미스로 감싸져 있지만 즉시 해결되고
then의 data는 여기서 반환된 데이터
이 경우 문자열이 될 겁니다 하지만 여기서는
프로미스인 함수의 결과를 다시 전달하고
이 프로미스가 해결될 때까지 즉 끝날 때까지 기다렸다가
다음 then 블록으로 넘어갑니다
이것이 프로미스 체이닝이고 매우 강력한 개념입니다
단계 안의 단계가 아니라 단계 다음 단계로 만들어주어
콜백 지옥에서 벗어나게 해줍니다
356. 프로미스 오류 처리하기
위 내용 프로미스 체인과 관련해서 설명하는데 위에걸 못 알아먹으니 이것도 못 알아먹네
캠프 제공 강의도 찾아보고 나중에 다시 봐야지
프로미스 체인에 추가할 수 있는
다른 메서드가 있습니다
가령 catch 메서드를 여기 추가할 수 있습니다
catch 메서드를 프로미스에 추가할 수 있는데
이 체인 어디에든 추가할 수 있어요
여기까지 오류 처리와 오류와 함께 프로미스 체인이 작동하는 방식을 배웠는데
이를 이해하는 것이 매우 중요합니다
오류를 우아한 방식으로 처리할 수 있는 훌륭한 애플리케이션을
작성하는 데 필요한 많은 기능과 유연성을 제공합니다
357. 프로미스 상태 & “finally”
여러분은 다양한 프로미스 상태에 대해 배웠습니다.
PENDING => 프로미스가 작동 중이며then()이나 catch() 가 실행되지 않습니다
RESOLVED => 프로미스가 해결됐습니다 => then()이 실행됩니다
REJECTED => · 프로미스가 거절됐습니다=> catch()이 실행됩니다
catch()나 then() 블록 다음에 또 다른 then() 블록이 있으면 프로미스가 PENDING 모드로 다시 들어갑니다. (참고: then()과 catch()는 항상 새로운 프로미스를 반환합니다 - 어떤 것으로도 해결되지 않거나then()내부에서 return한 것으로 해결됩니다). 더 이상 then() 블록이 남아 있지 않은 경우에만 아래의 최종 모드로 들어갑니다. SETTLED.
SETTLED가 되면 특수 블록인 finally()로 최종 정리 작업을 수행할 수 있습니다. 이전에 해결됐든 거부됐든 상관없이 finally()에는 도달합니다.
다음은 예시입니다:
somePromiseCreatingCode()
.then(firstResult => {
return 'done with first promise';
})
.catch(err => {
// would handle any errors thrown before
// implicitly returns a new promise - just like then()
})
.finally(() => {
// the promise is settled now - finally() will NOT return a new promise!
// you can do final cleanup work here
});
finally() 블록은 추가할 필요 없습니다(강의에서 하지 않았으니까요).
358. Async/ await
최신 JavaScript에는 기억해야할
정말 중요한 대체 구문이 있습니다
너무 중요해서 바로 말씀드리는 겁니다
여전히 프로미스를 활용하지만 then과 catch 메서드를
생략할 수 있어서 코드를 좀 더 동기 코드처럼
즉, 프로미스 없이 쓰는 일반적인 코드처럼
보이는 구문입니다 그것은 바로 async await입니다
async await이 무엇일까요? 함수에서 async await를
쓸 수 있습니다, 중요한 점은 함수에서만 쓸 수 있다는 겁니다
함수 키워드 앞에 async 키워드를
추가해서 이를 활성화합니다
여기에 async가 추가되면 함수가 내부적으로 바뀌거나
내부에서 일어나는 일이 보이지 않게 바뀝니다
async가 앞에 있으면 이 함수는 자동으로 프로미스를 반환합니다
여기 툴팁을 보면 이것이 프로미스를 반환한다고 알려줍니다
이 함수에서는 return을 호출하지 않는다는 점을 주목해 주세요
중첩된 함수에서는 호출하지만 이 함수 자체에서는 호출하지 않습니다
JavaScript가 지원하는 키워드인 async를 추가하면
이 전체 함수는 모든 내용을 하나의 큰 프로미스로 묶습니다
async를 추가했기 때문에 await 키워드를 쓸 수 있습니다 await을 프로미스 앞에 추가하는데
가령 이 getPosition이 프로미스를 반환하므로 그 앞에 await을 쓸 수 있습니다
그러면 프로미스 앞에 추가한 await은 무엇을 할까요?
프로미스가 해결되거나 실패하기를 기다리고 그렇게 되면 그 다음 줄이 실행됩니다
async await은 JavaScript의 작동이나
실행 방식을 바꾸는 게 아니라 코드를 내부적으로 변환합니다
따라서 다르게 생긴 코드가 나오고
실제로 여기에서 기다리고 있는 것처럼 보입니다
물론 이 함수에 있지만 이 함수에서만 기다리고 있는 겁니다
결국 여기 서로 연결된
then 메서드가 몇 개 있기 때문입니다
여기서 단점 없이 더 읽기 쉬운 코드를 갖는 이점을 얻을 수 있는데
마법처럼 JavaScript가
바뀌는 것이 아니라
then 같은 일들이 내부적으로 진행되고 있다는 것을
알아 두셔야 합니다
359. Async/ await & 오류 처리하기
async await로 어떻게 할까요?
try {
const posDate = await getPosition();
const timerDate = await setTimer(2000);
} catch (error) {
cosole.log(error);
}
try catch를 쓸 수 있습니다
여기 무언가를 try에 넣고
가령 이 코드를 넣고 생길 수 있는
오류를 catch에 넣고 그 에러를 catch 블록에서 처리합니다
가령 출력하거나 다른 원하는 작업으로 처리합니다
try 블록에 있는 모든 것들
즉, 여기에 있는 모든 단계 가령 여기 이 두 단계는
첫 번째 단계가 성공해야 실행됩니다
즉, 이 단계가 성공하면 이 단계도 실행되고
만일 이 단계가 실패하거나 즉, 프로미스가 거부되면
이 단계는 실행되지 않습니다
그러면 catch 블록으로 넘어갑니다
360. Async/ await vs "Raw 프로미스”
async await의 실제 단점은
동일한 함수 내에서 동시에 작업을 실행할 수 없다는 겁니다
361. Promise.all(), Promise.race()
Promise.race([getPosition(), setTimer(1000)]).then(data =>{
console.log(data);
});
두 프로미스가 있고 여기서 대문자 P로 Promise를 입력합니다
이는 프로미스의 생성자 함수인데
구성을 하는 대신에 이 프로미스 생성자 함수나
말하자면 Promise 객체에 정적 메서드를 호출할 수 있습니다
여기에 race 메서드를 써줍니다
race 메서드는 프로미스 배열을 받는데 가령 여기서는
getPosition과 setTimer의 프로미스 배열을 받게 됩니다
프로미스를 산출하는 두 개의 프로미스
즉 함수는 race로 전달된 후 race 자체도 가장 빠른 프로미스의
결과로 프로미스를 반환하고 여기에 race를 기반으로 일반적인
then catch 체인을 구축하면 일반 프로미스를 반환합니다
유일한 특별한 점은 데이터 반환이 가장 빠른 프로미스의 결과라는 겁니다
둘 다 동시에 시작되며 먼저 완료되는 것은 다음 catch 프로미스 체인에서 처리됩니다
반면 새로 고침 후 Block을 바로 클릭하면
프로미스가 실패하여 Done!이
보이지 않는데 이는 두 번째 프로미스가
버려지고 첫 번째 프로미스가 대신 끝났습니다
여기서 주목할 점은 이기지 못한 프로미스가
취소되는 것이 아니라 그 결과가 무시된다는 겁니다
그러나 HTTP 요청의 경우 요청이 아직 전송 중이면
결과가 무시된다는 점을 알아 두세요
따라서 race는 두 조건 중 하나에만
관심이 있고 그 프로미스의 결과를 쓰고
다른 조건은 무시하려고 하는 경우 유용할 수 있습니다
Promise.all([getPosition(), setTimer(1000)]).then(promiseData =>{
console.log(promiseData);
});
Promise.all을 써서 프로미스 배열을 전달하는 방법입니다
저는 이전에 썼던 프로미스와 같은 것을 전달하겠습니다
then까지 넣어주면 결과로
프로미스를 얻는데 이 프로미스의 데이터는 다른 프로미스들의
결합된 데이터입니다
여기 promiseData를 넣어주고 출력해보면
이것이 개별 프로미스가 해결된
모든 데이터의 조합임을 알 수 있습니다
보시다시피 promiseData는 Promise.all에 넣은
순서대로 각 프로미스에 의해 반환된 데이터를 가지는
배열입니다
getPosition은 처음에 setTimer는 두 번째에 있죠
여기로 돌아가보면 데이터 배열에는
첫 번째 인자로 getPosition의 결과가 들어가 있고
두 번째 인자로 setTimer의 결과가 들어있습니다
따라서 만일 모든 프로미스가 될 때까지 기다렸다가
결합된 데이터를 쓰고 싶다면 유용할 수 있습니다
Block을 클릭하면 오류만
뜹니다, 즉, 프로미스 중 하나가 실패
즉, 거부되면 다른 프로미스 역시 실행되지 않고
모두가 해결되거나 모두가 거부되기를 기다리는 것이 아니라
모두가 해결되길 바라지만 모두가 거부되는 것을
기다리고 있는 것은 아닙니다
대신 프로미스 중 하나가 거절되면 이는 취소되고
catch 블록으로 처리할 수 있는 오류가 뜨는 겁니다
따라서 모두 해결되거나 적어도 하나는 거부됩니다
모두 해결되거나 모두 거부될 때까지 기다릴 수도 있고
모두 성공했거나 모두 실패했는지
우려할 수도 있는데
promise.allSettled를 그럴 때 쓰면 됩니다
Promise.allSettled([getPosition(), setTimer(1000)]).then(promiseData =>{
console.log(promiseData);
});
객체로 첫 번째 프로미스의 출력을 볼 수 있는데
이는 거부되었고 두 번째 프로미스의 출력이 있는데 이는 수행됐습니다
새로 고침 후 Allow를 클릭하면 위치를 구하고 타이머가 만료되는
이 모든 것이 뜰 때까지 기다리면
설명은 물론 값과 함께
객체를 얻을 수 있습니다
그래서 프로미스가 해결됐기 때문에 여기에 구체적인 값인
Position과 타이머의 Done!이 결과로 나왔습니다
그렇게 약간 다른 데이터를 얻지만 여전히 배열이고
프로미스가 한 일을 설명하는 객체이며
여기서 흥미로운 점은 이제 이 자세한 설명을 얻었기 때문에
더 많은 유연성이 생겼다는 겁니다
다른 프로미스 중 하나가 거부된 경우에도
프로미스의 실행을 취소하지 않고 그 대신 대신 모든 프로미스를 완료하거나
확인한 다음 어떤 프로미스가 성공했고 실패했는지에 대한
자세한 요약을 제공해서
그에 담긴 지식을 활용할 수 있습니다
362. 마무리
다른 모듈은 건너뛰어도 반드시 이해해야 하는 핵심 모듈입니다
설명이 머리에 안들어오는 데 다시 반복 수강 하겠습니다
'JS' 카테고리의 다른 글
deep js - 2일차 (0) | 2024.05.18 |
---|---|
deep js - 1일차 (0) | 2024.05.17 |
객체 (1) | 2023.10.23 |
HTTP 요청으로 작업하기 (0) | 2023.10.20 |
JS 오늘 공부한 내용 (0) | 2023.10.16 |