관련 추천: "nodejs Tutorial"
JavaScript에 익숙한 친구들은 마우스 움직임, 마우스 클릭, 키보드 입력 등의 이벤트를 사용해 본 적이 있을 것입니다. 우리는 해당 처리를 트리거하기 위해 자바스크립트에서 이러한 이벤트를 수신합니다.
nodejs에도 이벤트가 있고, 특화된 처리를 위한 특별 이벤트 모듈도 있습니다.
동시 이벤트와 이벤트 루프도 nodejs에서 비동기 IO를 구축하는 데 매우 중요한 개념입니다.
오늘은 이에 대해 자세히 알아 보겠습니다.
nodejs는 이벤트 전용 모듈인 lib/events.js를 제공합니다.
nodejs를 사용하여 웹 서버를 구축하는 방법에 대해 이야기했던 때를 기억하시나요?
const server = http.createServer((req, res) => { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end('welcome to www.flydean.com\n') })
여기서 각 요청은 요청 이벤트를 트리거합니다.
nodejs의 핵심 API는 비동기식 이벤트 중심 아키텍처를 기반으로 하기 때문에 nodejs에는 많은 이벤트가 있습니다.
예: net.Server는 새 연결이 있을 때마다 이벤트를 트리거하고, fs.ReadStream은 파일이 열릴 때 이벤트를 트리거하며, stream은 데이터를 읽을 수 있을 때 이벤트를 트리거합니다.
nodejs 이벤트를 구축하는 방법을 살펴보겠습니다.
const EventEmitter = require('events') const eventEmitter = new EventEmitter()
이벤트에는 일반적으로 on과 Emit이라는 두 가지 메서드가 사용됩니다.
on은 이벤트를 수신하는 데 사용되고, Emit은 이벤트를 트리거하는 데 사용됩니다.
eventEmitter.on('fire', () => { console.log('开火') }) eventEmitter.emit('fire')
emit은 매개변수를 사용할 수도 있습니다.
eventEmitter.on('fire', who => { console.log(`开火 ${who}`) }) eventEmitter.emit('fire', '美帝')
두 매개변수를 다시 살펴보겠습니다.
eventEmitter.on('fire', (who, when) => { console.log(`开火 ${who} ${when}`) }) eventEmitter.emit('fire', '川建国','now')
기본적으로 EventEmitter는 등록 순서에 따라 모든 리스너를 동기적으로 호출합니다. 이는 이벤트의 올바른 순서를 보장하고 경쟁 조건 및 논리 오류를 방지하는 데 도움이 됩니다.
비동기 실행이 필요한 경우 setImmediate() 또는 process.nextTick()을 사용하여 비동기 실행 모드로 전환할 수 있습니다.
eventEmitter.on('fire', (who, when) => { setImmediate(() => { console.log(`开火 ${who} ${when}`); }); }) eventEmitter.emit('fire', '川建国','now')
또한 이벤트는 여러 가지 다른 메서드도 지원합니다.
once(): 단일 리스너 추가
removeListener() / off(): 이벤트에서 이벤트 리스너 제거
removeAllListeners(): 모든 이벤트 리스너 제거
우리는 nodejs 코드가 단일 스레드 환경에서 실행되고 한 번에 한 가지 작업만 처리한다는 것을 알고 있습니다.
이 처리 방법은 멀티 스레드 환경에서 데이터 동기화 문제를 방지하고 처리 효율성을 크게 향상시킵니다.
이벤트 루프라고 불리는 것은 프로그램 주기에서 프로세서가 이 주기의 이벤트를 처리한 후 다음 이벤트 주기에 들어가 다음 이벤트 주기의 이벤트를 처리한다는 것을 의미합니다.
이벤트 처리 중에 이벤트 처리가 차단되면 다른 이벤트 실행에도 영향을 미치게 되므로 JS에서는 거의 모든 IO가 Non-Blocking인 것을 알 수 있습니다. 이것이 JavaScript에 콜백이 너무 많은 이유이기도 합니다.
간단한 이벤트 루프 예시를 살펴보겠습니다:
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') action2() action3() } action1()
위 코드 출력:
action1 action2 action3
우리는 함수 간의 호출이 스택을 통해 구현된다는 것을 알고 있습니다. 호출 시퀀스도 스택을 통해 구현됩니다.
그러나 함수의 모든 메서드가 스택에 푸시되는 것은 아니며 일부 메서드는 메시지 대기열에 배치됩니다.
다른 예를 들어보겠습니다.
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') setTimeout(action2, 0) action3() } action1()
위 코드를 실행한 결과:
action1 action3 action2
결과가 다릅니다. 이는 settimeout이 타이머를 트리거하면 콜백 함수가 스택에 배치되는 대신 처리될 메시지 대기열에 배치되기 때문입니다.
이벤트 루프는 스택에 데이터가 없는 경우에만 메시지 대기열의 이벤트를 소비하도록 전환합니다.
위 예시에서 setTimeout의 타임아웃 시간은 0이지만, action3이 실행될 때까지 기다려야 실행될 수 있습니다.
setTimeout의 시간 제한은 현재 스레드에서 기다리지 않으며 브라우저나 다른 JS 실행 환경에 의해 호출됩니다.
ES6의 Promise에는 작업 대기열 개념이 도입되었습니다. 작업 대기열을 사용하면 비동기 함수의 결과를 호출 스택 끝에 배치하는 대신 최대한 빨리 실행합니다.
예:
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') setTimeout(action2, 0) new Promise((resolve, reject) => resolve('应该在action3之后、action2之前') ).then(resolve => console.log(resolve)) action3() } action1()
출력 결과:
action1 action3 应该在action3之后、action2之前 action2
이는 현재 함수가 끝나기 전에 해결된 Promise가 현재 함수 직후에 실행되기 때문입니다.
즉, 스택이 먼저 실행되고 그 다음 작업 큐가 실행되고 마지막으로 메시지 큐가 실행됩니다.
먼저 틱이라는 정의를 알려드리겠습니다. 틱은 이벤트 주기를 의미합니다. Process.nextTick()은 다음 이벤트 루프 틱이 시작되기 전에 이 함수를 호출하는 것을 의미합니다.
process.nextTick(() => { console.log('i am the next tick'); })
따라서 nextTick은 메시지 대기열의 setTimeout보다 빨라야 합니다.
nodejs는 코드를 최대한 빨리 실행할 수 있는 setImmediate 메서드를 제공합니다.
setImmediate(() => { console.log('I am immediate!'); })
setImmediate의 함수는 이벤트 루프의 다음 반복에서 실행됩니다.
setImmediate() 및 setTimeout(() => {}, 0)의 기능은 기본적으로 유사합니다. 이벤트 루프의 다음 반복에서 모두 실행됩니다.
특정 콜백 함수를 정기적으로 실행하려면 setInterval을 사용해야 합니다.
setInterval(() => { console.log('每隔2秒执行一次'); }, 2000)
要清除上面的定时任务,可以使用clearInterval:
const id = setInterval(() => { console.log('每隔2秒执行一次'); }, 2000) clearInterval(id)
注意,setInterval是每隔n毫秒启动一个函数,不管该函数是否执行完毕。
如果一个函数执行时间太长,就会导致下一个函数同时执行的情况,怎么解决这个问题呢?
我们可以考虑在回调函数内部再次调用setTimeout,这样形成递归的setTimeout调用:
const myFunction = () => { console.log('做完后,隔2s再次执行!'); setTimeout(myFunction, 2000) } setTimeout(myFunction, 2000)
更多编程相关知识,请访问:编程视频!!
위 내용은 nodejs의 이벤트 및 이벤트 루프 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!