실행 컨택스트
자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖는다.
싱글 스레드 방식으로 동작한다.
자바 스크립트의 동시성을 지원하는 것이 이벤트 루프(event loop) 이다.
v8과 같은 자바 스크립트 엔진은 콜스택을 순차적으로 실행하기만 한다.
비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진이 아니라 이를 구동하는 환경인
브라우저 또는 Node.js가 담당한다.
그림에서 JS전용이 실행 컨텍스트
톱니바퀴는 이벤트 루프를 나타내고 있다.
태스크 큐
task queue / event queue / callback queue
비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다.
마이크로큐
microtask queue / job queue
마이크로태스크 큐는 태스크 큐와 별도의 큐 이다.
프로미스의 후속 처리 메서드의 콜백 함수가 저장된다.
마이크로 태스크 큐는 태스킄 큐 보다 우선순위가 높다.
따라서 아래 코드의 출력 로그는 2 -> 3 -> 1 이다.
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
이벤트 루프
event loop 이벤트 루프는 실행 컨텍스트가 있는지, 태스크 큐에 대기중인 함수가 있는지 반복해서 확인한다.
콜 스택이 비어있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 태스크 큐에 대기중인 함수를 콜 스택으로 이동시킨다.
비동기 함수
비동기 함수를 호출하면 합수 내부의 비동기로 동작하는 코드가 바로 완료되지 않았다 해도 기다리지 않고 즉시 종료된다.
즉, 비동기 함수 내부의 비동기로 동작하는 코드는 비동기 함수가 종료된 이후(현재 콜 스택이 모두 사라지고 나서) 에 완료된다.
따라서, 비동기 함수 내부의 비동기로 동작하느 코드에서 처리 결과를 외부로 반환하거나
상위 스코프의 변수에 할당하면 기대한 대로 동작하지 않는다.
에러 처리의 한계
에러는 호출자 방향으로 전파된다. 즉, 콜 스택의 아래방향으로 전파된다. 따라서 콜백 함수가 발생시킨 에러는 catch 블록에서 캐치되지 않는다.
Promise
Promise - JavaScript | MDN
Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.
developer.mozilla.org
Using promises - JavaScript | MDN
Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다. 대부분 여러분은 이미 만들어진 promise를 사용했었기 때문에 이 가이드에서는 어떻게 promise를 만드는지 설명하기에 앞서 prom
developer.mozilla.org
Promise는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있습니다.
프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다.
다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'(프로미스)을 반환합니다.
상태
- 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
- 이행(fulfilled): 연산이 성공적으로 완료됨.
- 거부(rejected): 연산이 실패함.
예제
프로미스 생성
const promise = new Promise((resolve, reject) => {
// Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행한다.
if (condition) { //비동기 처리 성공
resolve('result');
}
else { //비동기 처리 실패
reject(new Error('failure reason'));
}
});
then
new Promise(resolve => resolve('fulfilled'))
.then(v /*fullfilled*/ => console.log(v));
new Promise(resolve => resolve('fulfilled'))
.then(console.log);
catch
new Promise((_, reject) => reject(new Error('reject')))
.catch(e => console.log(e));
new Promise((_, reject) => reject(new Error('reject')))
.then(undefined, e => console.log(e));
finally
new Promise(() => {})
.finally(() => console.log('finally'));
Worker
https://nodejs.org/api/worker_threads.html
Worker threads | Node.js v20.5.0 Documentation
Worker threads# Source Code: lib/worker_threads.js The node:worker_threads module enables the use of threads that execute JavaScript in parallel. To access it: const worker = require('node:worker_threads'); copy Workers (threads) are useful for performing
nodejs.org
아주 간단한 사용법 - 1 (파일을 분리했을 경우)
main.js
import { Worker } from 'worker_threads';
const worker = new Worker("./worker.js"); // 자식스레드를 생성해서 worker.js의 내용을 실행
console.log("main");
worker.js
console.log("thread!")
아주 간단한 사용법 - 2 (하나의 파일)
main.js
import { Worker, isMainThread } from 'worker_threads';
if(isMainThread){
const worker = new Worker("./main.js");
console.log("main!");
}else{
console.log("thread!");
}
메인스레드와 자식스레드간 상호작용 - 메시지 주고받기
main.js
import { Worker } from 'worker_threads';
const worker = new Worker("./worker.js"); // 자식스레드를 생성해서 worker.js의 내용을 실행
worker.on('message', (msg) => { // 자식스레드에서 postMessage 함수를 통해서 발생한 메시지(이벤트)에 대한 리스너
console.log(msg);
});
console.log("main!");
worker.js
import { parentPort } from "worker_threads";
console.log("thread! - start");
parentPort.postMessage("======= thread is working....======="); // 자식쓰레드가 실행되는 도중에 메인쓰레드로 메시지(이벤트) 전달 - 해당 라인을 여러개 복사해서 실행해보세요.
console.log("thread! - end");
이벤트:'error'
- err <오류>
'error'작업자 스레드가 포착되지 않은 예외를 발생시키면 이벤트가 발생합니다 . 이 경우 작업자가 종료됩니다.
이벤트:'exit'
- exitCode <정수>
'exit'작업자가 중지되면 이벤트가 발생합니다 . 작업자가 를 호출하여 종료된 경우 process.exit()매개 exitCode변수는 전달된 종료 코드입니다. 작업자가 종료된 경우 exitCode매개변수는 입니다 1.
이것은 모든 인스턴스에서 발생하는 최종 이벤트입니다 Worker.
이벤트:'message'
- value 전송된 값
'message'작업자 스레드가 호출되면 이벤트가 발생 합니다 require('node:worker_threads').parentPort.postMessage(). port.on('message')자세한 내용은 이벤트를 참조하세요 .
작업자 스레드에서 보낸 모든 메시지는 개체에서 'exit'이벤트가 발생 하기 전에 발생합니다 Worker.
이벤트:'messageerror'
- error <오류> 오류 개체
'messageerror'메시지 역직렬화에 실패하면 이벤트가 발생합니다 .
이벤트:'online'
'online'작업자 스레드가 JavaScript 코드 실행을 시작하면 이벤트가 발생합니다 .
동기적으로 순서를 맞추는 방법 - Promise
main.js
import { Worker } from 'worker_threads';
new Promise((resolve) => {
const worker = new Worker("./worker.js");
worker.on("message", (msg)=>{ // postMessage에 대한 이벤트 리스너
console.log(msg);
});
worker.on("exit", (code)=>{ // 자식스레드가 종료하면 발생하는 이벤트에 대한 리스너
resolve(); // 자식스레드가 종료됐을 때 resolve 함수로 promise의 상태를 Fulfilled로 변경해준다.
});
}).then(()=>{
console.log("main");
});
new Worker로 자식스레드를 실행하는 부분을 Promise로 감싸고, 자식스레드의 동작이 종료되었을 때, resolve()를 통해서 Promise의 상태를 thenable하게 변경해주면
메인스레드에서 자식스레드의 동작이 종료 다 종료되고 나서 console.log(”main”)을 실행하도록 코드를 작성할 수 있다.
worker.js
console.log("thread! - start");
parentPort.postMessage("======= thread is working 1....=======");
parentPort.postMessage("======= thread is working 2....=======");
console.log("thread! - end");
main.js
const {
Worker, isMainThread, parentPort, workerData,
} = require('node:worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: script,
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
} else {
const { parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}