동기적: 하나의 작업이 끝나기 전까지 다음 작업을 실행하지 않음
<모든 작업은 메인 스레드에서 실행함>
비동기적: 특정 작업이 완료되었을 때 실행할 콜백을 등록해두고 바로 다음 작업을 실행함
<이 때 특정 작업은 백그라운드에서 실행함>
콜백(리스너): 특정 작업이 완료되었을 때 실행될 함수
Node.js는 비동기 실행 중심의 실행환경이기 때문에 일반적인 경우 비동기 함수를 사용해야 함!
동기 실행과 비동기 실행 비교
(전처리 작업) 모듈이 있는 디렉토리에 new.txt를 만들고 텍스트 입력 및 저장합니다.
동기 실행
// 동기 실행 소스 const fs=require('fs'); console.log('Start'); // new파일의 내용을 가져오기 let content=fs.readFileSync('./new.txt','utf8'); console.log(content); console.log('Finish');
fs모듈의 readFileSync함수는 파일 내용을 가져오는 동기 함수입니다.
동기 함수는 위와 같이 순차적으로 실행됩니다.
비동기 실행
// 비동기 실행 소스 const fs=require('fs'); console.log('Start'); // new파일의 내용을 가져오기 // 일단 콜백을 등록해두고 바로 다음 코드로 실행을 넘김 // 콜백은 readFile실행이 완전히 끝나면 실행됨 // readFile은 백그라운드(다른 스레드)로 작업이 넘겨지고 다음 코드들이 모두 처리되고 나서야 실행된다. fs.readFile('./new.txt','utf8',(error,data)=>{ console.log(data); // 콜백 등록 }); console.log('Finish');
fs모듈의 readFileSync함수는 파일 내용을 가져오는 비동기 함수입니다.
비동기 함수 실행이 끝나면 리턴 값(파일 내용)을 콜백(data매개변수)에 넘겨서 호출하는 구조입니다.
이 때 콜백은 비동기 함수의 세 번째 인수로 등록합니다.
비동기 함수는 다음 작업(코드)들이 모두 처리된 후 실행됩니다.
결국 console.log(‘Start’); -> fs.readFile(‘./new.txt’); <백그라운드로 넘김> -> console.log(‘Finish’); -> fs.readFile(‘./new.txt’); <백그라운드에서 마친 작업을 실행> -> console.log(data); 순서로 실행됩니다.
비동기 작업은 파일을 읽는 것과 같이 작업량이 많을 때 메인 스레드의 부하를 줄여 줍니다.
Node.js 주의점
노드에서 ‘CPU 수치 계산 작업’과 ‘작업 지시’는 메인 스레드가 담당합니다.
만약 수치 계산 작업이 오래 걸리는 일이라면, 다른 요청들은 처리하지 못하고 메인 작업에 집중하는 상황이 발생합니다. 왜냐하면 메인 스레드에서 다른 스레드에게 작업을 지시해야 되는데, 자신이 너무 큰 일을 하고 있어서 일을 분담하지도 못 하는 것입니다. 따라서 노드는 ‘영상 처리’ 같은 대용량 작업을 처리하기에는 부적절한 실행 환경입니다.
이벤트
이벤트: 어떤 일이 발생했음을 알리는 신호
이벤트 기반 프로그래밍에서 콜백은 이벤트 핸들러라고도 합니다.
// EvenEmiiter는 events모듈이 보내는 생성자 함수를 받음 // js에서 생성자는 클래스를 모방하는 함수로, 객체 생성 시에는 java와 똑같이 new키워드로 인스터스화하면 된다. const EventEmitter = require('events'); // 생성자 함수로부터 객체를 만들고 myEmitter가 해당 객체를 저장 const myEmitter = new EventEmitter(); // 'test'라는 특정 이벤트가 발생하면 이벤트 핸들러(콜백) 실행 myEmitter.on('test',()=>{ console.log('Success!'); }); let num=1+2; // 어떤 일: 1+2를 계산한 값을 num변수에 저장함 // emit: 이벤트 발송(발생시키기) myEmitter.emit('test');
‘JavaScript 기본 개념 (함수,객체,모듈)’ 게시글을 보고 오셨다면 첫 주석부터 의아하실 수 있습니다.
require함수는 인수에 적힌 모듈의 module.exports속성이 가리키는 객체를 반환합니다.
그러나 위처럼 인수에 적힌 모듈(events)에 위치한 생성자 or 일반 함수를 반환할 수도 있습니다.
후자의 대표적인 케이스가 위와 같이 events모듈을 이용할 때입니다.
require함수로부터 events모듈의 생성자를 받고 EventEmitter객체를 생성했습니다.
이후 myEmitter가 해당 객체를 저장합니다.
이 객체는 on과 emit이라는 메소드를 가지고 있습니다.
on은 특정 이벤트가 발생하면 콜백을 실행하는 메소드입니다.
emit은 이벤트(신호)를 발송하는 메소드입니다.
on메소드 호출 시, 첫 번째 인수에 특정 이벤트를 지정하고 두 번째 인수에 콜백을 등록합니다.
emit메소드 호출 시, 인수에 이벤트 이름을 기재합니다.
노드에서 이벤트는 ‘어떤 일이 발생했음을 알리는 신호’입니다.
따라서 ‘어떤 일’에 대한 소스 바로 뒤에 emit메소드로 이벤트를 발송하는 것이 일반적입니다.
위 프로그램은 ‘어떤 일(1+2를 계산한 값을 num변수에 저장함)’이 발생한 뒤 콜백(Success출력 함수)을 실행합니다.
다음 작업들이 모두 처리돼야 콜백을 실행하는 ‘비동기 함수’보다 ‘이벤트 기반 프로그래밍’이 더욱 효과적입니다!!
EventEmitter 객체 사용법
1) EventEmitter객체를 사용할 땐, 이벤트를 발생시키기 전 콜백 설정을 해두어야 한다!
이벤트를 발생시키고 콜백 설정을 해봤자 이벤트가 오지 않기 때문이다.
2) 하나의 이벤트에 대해 여러 개의 콜백도 설정할 수 있다.
3) 이벤트를 발송되고 콜백이 실행되는 현상은 하나의 EventEmitter객체 내에서만 이루어진다.
const EventEmitter = require('events'); // EventEmitter객체 두 개 생성 const myEmitter1 = new EventEmitter(); const myEmitter2 = new EventEmitter(); // on메소드: 'test'라는 특정 이벤트가 발생하면 콜백 실행 myEmitter1.on('test',()=>{ console.log('(1-1) Success!'); }); myEmitter1.on('test',()=>{ console.log('(1-2) Success!'); }); myEmitter2.on('test',()=>{ console.log('(2-1) Success2!'); }); // emit메소드: 이벤트 발송(발생시키기) myEmitter1.emit('test'); myEmitter2.emit('test');
EventEmitter객체 사용법의 (3)번 과정에 대한 소스입니다.
EventEmitter가 총 2개 생성 돼있습니다.
(정확하게는 EventEmitter객체를 저장한 myEmitter1,myEmitter2객체가 2개 생성되었지만 위처럼 표현합니다.)
‘이벤트가 발송되고 콜백이 실행되는 현상은 하나의 EventEmitter객체 내에서만 이루어진다.’
이 문구의 의미는 아래 그림 화살표를 살펴보면 됩니다.
myEmitter1 객체의 emit메소드로부터 이벤트가 발송되면 myEmitter1의 on메소드에 등록된 콜백만 실행됩니다.
myEmitter2 객체의 on메소드에서도 test이벤트가 오면 콜백을 하지만 실행되지 않는 모습입니다.
마찬가지로,
myEmitter2 객체의 emit메소드로부터 이벤트가 발송되면 myEmitter2의 on메소드에 등록된 콜백만 실행됩니다.
EventEmitter객체의 여러 메소드
once 메소드: 콜백을 등록하는데, 딱 한 번만 이벤트에 대해서 반응하도록 한다.
const EventEmitter = require('events'); // EventEmitter객체 생성 const myEmitter = new EventEmitter(); myEmitter.once('test',()=>{ console.log("Success!"); }); myEmitter.emit('test'); myEmitter.emit('test'); myEmitter.emit('test');
listernes메소드: 여러 개의 이벤트 핸들러(콜백)를 출력해주는 메소드
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); const add=()=>{console.log(1+2);}; const subtract=()=>{console.log(1-2);}; // on메소드: 'test'라는 특정 이벤트가 발생하면 콜백 실행 myEmitter.on('test',add); myEmitter.on('test',subtract); myEmitter.emit('test'); console.log(myEmitter.listeners('test'));
Off 메소드: 이벤트 핸들러(콜백)을 해제하는 메소드
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); const add=()=>{console.log(1+2);}; const subtract=()=>{console.log(1-2);}; // on메소드: 'test'라는 특정 이벤트가 발생하면 콜백 실행 myEmitter.on('test',add); myEmitter.off('test',add); myEmitter.emit('test');
이벤트에 추가 정보를 함께 전달하기
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); // 'test'라는 특정 이벤트가 발생하면 콜백 실행 // 추가 정보는 어로우 함수에서 처리됨 myEmitter.on('test',(obj)=>{ console.log(obj); }); // obj객체 생성 const obj={type:'text',data:'Hello sha',date:'2021-05-26'}; // emit: 이벤트 발송(발생시키기) myEmitter.emit('test',obj);
obj객체를 만들고 emit메소드의 두 번째 인수에 넣어서 전달하였습니다.
전달 시 콜백 함수의 매개변수로 접근할 수 있습니다.
감사합니다.
Node에 관한 게시물은 CodeIt,생활코딩의 강의 내용을 정리한 글입니다. 강의 이미지나 내용 자체를 업로드하지는 않습니다!!