자바스크립트 엔진
- 자바스크립트 엔진은 하나의 메인 스레드로 구성된다.
- 메인 스레드는 코드를 읽어서 한 줄씩 실행한다.
- 브라우저 환경에서는 유저 이벤트를 처리하고 화면을 그린다.
동기적 제어 흐름
- 동기적 제어 흐름은 현재 실행 중인 코드가 종료되기 전까지 다음 줄의 코드를 실행하지 않는 것을 의미한다.
- 분기문, 반복문, 함수 호출 등이 동기적으로 실행된다.
- 코드의 흐름과 실제 제어 흐름이 동일하다.
- 싱글 스레드 환경에서 메인 스레드를 긴 시간 점유하면, 프로그램을 멈추게 한다.
비동기적 제어 흐름
- 비동기적 제어 흐름은 현재 실행 중인 코드가 종료되기 전에 다음 라인의 코드를 실행하는 것을 의미한다.
- Promise, Callback function을 호출하는 함수 등은 비동기적으로 실행된다.
- 코드 흐름과 실제 제어 흐름이 다르다.
- 비동기 작업을 기다리는 동안 메인 스레드는 다른 작업을 처리한다.
자바스크립트 비동기
- 자바스크립트 엔진은 비동기 처리를 제공하지 않는다.
- 대신, 비동기 코드는 정해진 함수를 제공하여 활용할 수 있다.
- 이 함수들을 API라고 한다.
- 비동기 API의 예시로, setTimeout, XMLHttpRequest, fetch등의 Web API가 있다.
- node.js의 경우 파일 처리 API, 암호화 API 등을 제공한다.
비동기 처리 모델
- 비동기 코드를 처리하는 모듈은 자바스크립트 엔진의 외부에 있다.
- 이벤트 루프(Event loop), 태스크 큐(Task queue), 잡 큐(Job queue)등으로 구성된다.
- API 모듈은 비동기 요청을 처리 후 태스크 큐에 콜백 함수를 넣는다.
- 자바스크립트 엔진은 콜 스택이 비워지면, 태스크 큐의 콜백 함수를 실행한다.
태스크 큐는 들어온 순서대로 콜백 함수를 내보낸다.
-이벤트 루프는 콜 스택이 비워졌을 때, 태스크 큐에서 콜백 함수가 들어온 순서대로 함수를 내보낸다. 단, 큐는 여러 개가 존재하며 그 중 우선수위가 높은 큐(잡 큐 등)에서 나중에 들어온 콜백 함수가 실행될 수 있다.
Promise
- Promise API는 비동기 API 중 하나이다.
- 태스크 큐가 아닌 잡 큐(Job queue, 혹은 microtask queue)를 사용한다.
- 잡 큐는 태스크 큐보다 우선순위가 높다.
- Promise를 사용하면 비동기 메서드를 동기 메서드처럼 값을 반환할 수 있다. // 최종결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 "약속(promise)"를 반환한다.
- 비동기 작업을 표현하는 자바스크립트 객체
- 비동기 작업의 진행,성공,실패 상태를 표현한다.
- 비동기 처리의 순서를 표현할 수 있다.
setTimeout(() => {
console.log("Timeout1");
},0);
Promise.resolve().then(() => console.log("Promise1"));
setTimeout(() => {
console.log("Timeout2"):
},0);
Promise.resolve().then(() => console.log("Promise2"));
//Promise1 Promise2
//Timeout1 Timeout2
//프로미스가 타임아웃보다 먼저 실행된 것을 볼 수 있음
Promise의 역할
주로 웹 서비스를 구현시 원활한 데이터 통신을 위해 사용된다. 서버에 데이터를 요청했을 때 데이터가 모두 받아오기 전에 웹에 출력하려고 할 때 발생하는 오류를 방지하기 위해 활용된다. 즉, Promise 객체는 A,B,C 로직이 있을 때, A로직이 모두 완료될 때까지 B,C로직을 대기시키지 않고 실행시키는 것에 주로 사용.
참조 : https://heytech.tistory.com/245
Promise의 상태
- pending(대기) :이행하지도, 거부하지도 않은 초기 상태
- Fulfilled(이행) : 연산이 성공적으로 완료됨
- Rejected(실패) : 연산이 실패함
후속 처리 메서드
프로미스로 구현된 비동기 함수를 호출하는 측에서는 프로미스 객체의 후속 처리 메소드(then, catch)를 통해 비동기 처리 결과 또는 에러 메세지를 전달받아 처리합니다.
then
- then 메소드는 두 개의 콜백 함수를 인자로 전달 받습니다.
- 첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 경우)시에 실행됩니다.
- 두 번째 콜백 함수는 실패(rejected, reject 함수가 호출된 경우)시에 실행됩니다.
- then 메소드는 기본적으로 프로미스를 반환합니다.
catch
- catch 메소드는 비동기 처리 혹은 then 메소드 실행 중 발생한 에러(예외)가 발생하면 호출됩니다.
- catch 메소드 역시 프로미스를 반환합니다.
참조 : https://yoo11052.tistory.com/155
let promise = new
Promise((resole, reject) =>
{
if (Math.random() < 0.5 {
return reject("실패");
}
resolve(10)
})
- new Promise(callback)
- callback 함수는(resole, reject)두 인자를 받는다.
- Promise가 호출에 성공하였을 때 resolve를 호출한다.
- Promise가 호출에 실패하였을 때 reject를 호출한다.
promise
.then(data => {
console.log("성공, data);
})
.catch(e -> {
console.log("실패, e);
})
.finally(() => {
console.log("promise 종료");
})
- then() 메서드에 성공했을 때 실행할 콜백 함수를 인자로 넘긴다.
- catch() 메서드에 실패했을 때 실행할 콜백 함수를 인자로 넘긴다.
- finally() 메서드는 성공/실패 여부와 상관없이 모두 실행할 콜백 함수를 인자로 넘긴다.
- then(callback1,callback2)로 callback1의 자리에 성공,callback2의 자리에 실패 메서드를 인자로 넣을 수 있다.
promise
.then(data => {
return fetchUser(data);
})
.then(user -> {
console.log('user: ',user);
})
.catch(e => {
console.log('실패: ',e);
})
- then/catch 메서드가 또 다른 promise를 리턴하여, 비동기 코드에 순서를 부여한다.
- 이렇게 동일한 객체에 메서드를 연결할 수 있는 것을 체이닝(chaining)이라 한다.
- 함수를 호출한 주체가 함수를 끝낸 뒤 자기 자신을 리턴하도록 구현한다.
promise는 인위적으로 Promise 메서드 체인을 만들 수 있다. 즉 promise를 리턴하는 함수를 만들어서 그 함수를 호출할 필요가 없이 성공 혹은 실패를 바로 호출할 수 있다. // 비동기 코드로 진행해야 하는 상황 등에 유용하게 사용 가능
정적 메소드
Promise.resolve
Promise.resolve 메소드는 인자값을 래핑하여 프로미스를 반환합니다. (fulfilled)
const promise = Promise.reject('failed') // new Promise((resolve, reject) => reject('failed'))
promise.catch(error => console.log('This is in the catch ' + error))
This is in the then success
Promise.reject
Promise.reject 메소드역시 인자값을 래핑하여 프로미스를 반환합니다. (rejected)
const promise = Promise.reject('failed') // new Promise((resolve, reject) => reject('failed'))
promise.catch(error => console.log('This is in the catch ' + error))
This is in the catch failed
Promise.all
Promise.all 메소드는 프로미스가 담겨있는 배열과 같은 이터러블 객체를 인자로 받습니다.
인자로 전달받은 모든 프로미스를 병렬로 처리하고 그 결과값을 배열에 담아 resolve로 반환합니다.
만약 여러개의 프로미스를 순차적으로 처리한다면 어떻게 될까요?
첫번째 프로미스가 종료된후에 두번째 프로미스가 실행되고, 두번째 프로미스가 종료된후에 세번째 프로미스가 실행되게 됩니다.
물론 프로미스가 다른 프로미스를 의존하고 있는 경우에는 순차적으로 처리할 필요가 있지만 다음의 경우에는 서로 의존하고있지 않기 때문에 순차적으로 처리할 필요가 없습니다.
const promise1 = () => new Promise(resolve => setTimeout(() => resolve(1), 1000))
const promise2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000))
const promise3 = () => new Promise(resolve => setTimeout(() => resolve(3), 3000))
promise1().then(result => {
console.log(result) // 프로그램을 실행하고 1초뒤에 수행됨
return promise2()
}).then(result => {
console.log(result) // 프로그램을 실행하고 3초뒤에 수행됨 (1 + 2)
return promise3()
}).then(result => {
console.log(result) // 프로그램을 실행하고 6초뒤에 수행됨 (1 + 2 + 3)
})
1
2
3
때문에 서로 의존관계이지 않은 여러 프로미스들을 이터러블 객체에 담아 Promise.all 메소드를 이용해 한번에 병렬처리 할 수 있습니다.
가장 마지막으로 끝나는 프로미스를 기준으로 수행되고, 모든 프로미스가 fullfilled 상태가 되면 결과값을 배열에 담아 새로운 프로미스를 반환합니다.
프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료합니다.
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise(resolve => setTimeout(() => resolve(2), 2000)),
new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log) // 프로그램을 실행하고 3초뒤에 실행됨
.catch(console.log)
[ 1, 2, 3 ]
Promise.race
Promise.race 메소드는 Promise.all 메소드와 동일하게 프로미스가 담겨있는 이터러블 객체를 인자로 받지만, Promise.all 과 달리 병렬로 처리하지 않고 가장 먼저 끝나는 프로미스의 결과값을 resolve로 반환합니다.
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise(resolve => setTimeout(() => resolve(2), 2000)),
new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log)
.catch(console.log)
3
Promise.allSettled
Promise.allSettled 메소드역시 Promise.all 메소드와 동일하게 프로미스가 담겨있는 이터러블 객체를 인자로 받고 병렬로 처리합니다.
다만 Promise.all 의 경우 프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료하게되지만, Promise.allSettled 메소드의 경우 rejected 상태가 되어도 수행을 종료하지않고, 프로미스가 수행된 상태와 결과값을 배열에 담아 resolve로 반환합니다.
Promise.allSettled([
new Promise(resolve => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(2), 2000))
]).then(console.log)
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 2 }
]
Promise.all 메소드와 또다른 차이점은 각각의 프로미스 처리결과를 객체로 나타내고 status 프로퍼티를 가지게됩니다. fullfilled 상태인 경우 value 프로퍼티를 가지게되고, rejected 상태인 경우 reason 프로퍼티를 가지게 됩니다.
'Elice' 카테고리의 다른 글
[TIL]엘리스트랙 5주차 1일 Node.js (0) | 2023.09.05 |
---|---|
[TIL]엘리스트랙 4주차 3일 (0) | 2023.08.30 |
[TIL]엘리스트랙 3주차 Javascript (0) | 2023.08.27 |
[TIL] 엘리스트랙 Javascript (0) | 2023.08.24 |
[TIL]엘리스트랙 Javascript (0) | 2023.08.23 |