비동기 컨텍스트 추적
[Stable: 2 - Stable]
Stable: 2 Stability: 2 - Stable
소스 코드: lib/async_hooks.js
소개
이 클래스들은 상태를 연결하고 콜백과 Promise 체인 전체에 전파하는 데 사용됩니다. 웹 요청 또는 기타 비동기 작업의 수명 동안 데이터를 저장할 수 있습니다. 다른 언어의 스레드 로컬 스토리지와 유사합니다.
AsyncLocalStorage
및 AsyncResource
클래스는 node:async_hooks
모듈의 일부입니다.
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks')
클래스: AsyncLocalStorage
[히스토리]
버전 | 변경 사항 |
---|---|
v16.4.0 | AsyncLocalStorage가 이제 Stable입니다. 이전에는 Experimental이었습니다. |
v13.10.0, v12.17.0 | 추가됨: v13.10.0, v12.17.0 |
이 클래스는 비동기 작업을 통해 일관성을 유지하는 저장소를 만듭니다.
node:async_hooks
모듈을 기반으로 자체 구현을 만들 수 있지만, AsyncLocalStorage
는 성능이 뛰어나고 메모리 안전한 구현이며 구현하기 어려운 중요한 최적화를 포함하고 있으므로 선호해야 합니다.
다음 예제에서는 AsyncLocalStorage
를 사용하여 들어오는 HTTP 요청에 ID를 할당하고 각 요청 내에서 로깅된 메시지에 포함하는 간단한 로거를 만듭니다.
import http from 'node:http'
import { AsyncLocalStorage } from 'node:async_hooks'
const asyncLocalStorage = new AsyncLocalStorage()
function logWithId(msg) {
const id = asyncLocalStorage.getStore()
console.log(`${id !== undefined ? id : '-'}:`, msg)
}
let idSeq = 0
http
.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start')
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Prints:
// 0: start
// 1: start
// 0: finish
// 1: finish
const http = require('node:http')
const { AsyncLocalStorage } = require('node:async_hooks')
const asyncLocalStorage = new AsyncLocalStorage()
function logWithId(msg) {
const id = asyncLocalStorage.getStore()
console.log(`${id !== undefined ? id : '-'}:`, msg)
}
let idSeq = 0
http
.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start')
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish')
res.end()
})
})
})
.listen(8080)
http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Prints:
// 0: start
// 1: start
// 0: finish
// 1: finish
AsyncLocalStorage
의 각 인스턴스는 독립적인 저장소 컨텍스트를 유지합니다. 여러 인스턴스가 서로의 데이터를 방해할 위험 없이 동시에 안전하게 존재할 수 있습니다.
new AsyncLocalStorage()
[히스토리]
버전 | 변경 사항 |
---|---|
v19.7.0, v18.16.0 | onPropagate 옵션 제거 |
v19.2.0, v18.13.0 | onPropagate 옵션 추가 |
v13.10.0, v12.17.0 | 추가: v13.10.0, v12.17.0 |
AsyncLocalStorage
의 새 인스턴스를 생성합니다. 저장소는 run()
호출 내부 또는 enterWith()
호출 이후에만 제공됩니다.
정적 메서드: AsyncLocalStorage.bind(fn)
추가: v19.8.0, v18.16.0
주어진 함수를 현재 실행 컨텍스트에 바인딩합니다.
정적 메서드: AsyncLocalStorage.snapshot()
추가: v19.8.0, v18.16.0
- 반환값: <함수>
(fn: (...args) : R, ...args) : R
시그니처를 가진 새 함수
현재 실행 컨텍스트를 캡처하고 인수로 함수를 받는 함수를 반환합니다. 반환된 함수가 호출될 때마다 캡처된 컨텍스트 내에서 전달된 함수를 호출합니다.
const asyncLocalStorage = new AsyncLocalStorage()
const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot())
const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()))
console.log(result) // 123을 반환합니다
AsyncLocalStorage.snapshot()
은 간단한 비동기 컨텍스트 추적 목적으로 AsyncResource
사용을 대체할 수 있습니다. 예를 들어:
class Foo {
#runInAsyncScope = AsyncLocalStorage.snapshot()
get() {
return this.#runInAsyncScope(() => asyncLocalStorage.getStore())
}
}
const foo = asyncLocalStorage.run(123, () => new Foo())
console.log(asyncLocalStorage.run(321, () => foo.get())) // 123을 반환합니다
asyncLocalStorage.disable()
추가됨: v13.10.0, v12.17.0
[Stable: 1 - Experimental]
Stable: 1 Stability: 1 - Experimental
AsyncLocalStorage
인스턴스를 비활성화합니다. asyncLocalStorage.run()
또는 asyncLocalStorage.enterWith()
가 다시 호출될 때까지 asyncLocalStorage.getStore()
에 대한 모든 후속 호출은 undefined
를 반환합니다.
asyncLocalStorage.disable()
을 호출하면 인스턴스에 연결된 모든 현재 컨텍스트가 종료됩니다.
asyncLocalStorage
가 가비지 컬렉션되기 전에 asyncLocalStorage.disable()
을 호출해야 합니다. 이는 asyncLocalStorage
가 제공하는 저장소에는 적용되지 않습니다. 해당 객체는 해당 비동기 리소스와 함께 가비지 컬렉션됩니다.
현재 프로세스에서 asyncLocalStorage
가 더 이상 사용되지 않을 때 이 메서드를 사용합니다.
asyncLocalStorage.getStore()
추가됨: v13.10.0, v12.17.0
- 반환값: <any>
현재 저장소를 반환합니다. asyncLocalStorage.run()
또는 asyncLocalStorage.enterWith()
를 호출하여 초기화된 비동기 컨텍스트 외부에서 호출하면 undefined
를 반환합니다.
asyncLocalStorage.enterWith(store)
추가됨: v13.11.0, v12.17.0
[Stable: 1 - Experimental]
Stable: 1 Stability: 1 - Experimental
store
<any>
현재 동기 실행의 나머지 기간 동안 컨텍스트로 전환한 다음 후속 비동기 호출을 통해 저장소를 유지합니다.
예시:
const store = { id: 1 }
// 주어진 저장소 객체로 이전 저장소를 대체합니다.
asyncLocalStorage.enterWith(store)
asyncLocalStorage.getStore() // 저장소 객체를 반환합니다.
someAsyncOperation(() => {
asyncLocalStorage.getStore() // 동일한 객체를 반환합니다.
})
이 전환은 전체 동기 실행 동안 계속됩니다. 예를 들어, 이벤트 핸들러 내에서 컨텍스트가 입력되면 후속 이벤트 핸들러도 AsyncResource
를 사용하여 다른 컨텍스트에 특별히 바인딩되지 않는 한 해당 컨텍스트 내에서 실행됩니다. 따라서 강력한 이유가 없다면 run()
이 enterWith()
보다 선호되어야 합니다.
const store = { id: 1 }
emitter.on('my-event', () => {
asyncLocalStorage.enterWith(store)
})
emitter.on('my-event', () => {
asyncLocalStorage.getStore() // 동일한 객체를 반환합니다.
})
asyncLocalStorage.getStore() // undefined를 반환합니다.
emitter.emit('my-event')
asyncLocalStorage.getStore() // 동일한 객체를 반환합니다.
asyncLocalStorage.run(store, callback[, ...args])
추가됨: v13.10.0, v12.17.0
store
<any>callback
<Function>...args
<any>
컨텍스트 내에서 함수를 동기적으로 실행하고 그 반환 값을 반환합니다. store는 콜백 함수 외부에서는 접근할 수 없습니다. store는 콜백 내에서 생성된 모든 비동기 작업에 접근할 수 있습니다.
선택적 args
는 콜백 함수에 전달됩니다.
콜백 함수에서 오류가 발생하면 run()
에서도 오류가 발생합니다. 스택 추적은 이 호출의 영향을 받지 않으며 컨텍스트를 종료합니다.
예시:
const store = { id: 2 }
try {
asyncLocalStorage.run(store, () => {
asyncLocalStorage.getStore() // store 객체를 반환합니다
setTimeout(() => {
asyncLocalStorage.getStore() // store 객체를 반환합니다
}, 200)
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // undefined를 반환합니다
// 여기서 오류가 catch됩니다
}
asyncLocalStorage.exit(callback[, ...args])
추가됨: v13.10.0, v12.17.0
[Stable: 1 - Experimental]
Stable: 1 Stability: 1 - 실험적
callback
<Function>...args
<any>
컨텍스트 외부에서 함수를 동기적으로 실행하고 그 반환 값을 반환합니다. store는 콜백 함수 또는 콜백 내에서 생성된 비동기 작업 내에서는 접근할 수 없습니다. 콜백 함수 내에서 수행되는 모든 getStore()
호출은 항상 undefined
를 반환합니다.
선택적 args
는 콜백 함수에 전달됩니다.
콜백 함수에서 오류가 발생하면 exit()
에서도 오류가 발생합니다. 스택 추적은 이 호출의 영향을 받지 않으며 컨텍스트로 다시 진입합니다.
예시:
// run 호출 내에서
try {
asyncLocalStorage.getStore() // store 객체 또는 값을 반환합니다
asyncLocalStorage.exit(() => {
asyncLocalStorage.getStore() // undefined를 반환합니다
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // 동일한 객체 또는 값을 반환합니다
// 여기서 오류가 catch됩니다
}
async/await
와 함께 사용
비동기 함수 내에서 컨텍스트 내에서 실행될 await
호출이 하나뿐인 경우 다음 패턴을 사용해야 합니다.
async function fn() {
await asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('key', value)
return foo() // foo의 반환값이 await됩니다.
})
}
이 예제에서 저장소는 콜백 함수와 foo
에서 호출되는 함수 내에서만 사용할 수 있습니다. run
외부에서 getStore
를 호출하면 undefined
가 반환됩니다.
문제 해결: 컨텍스트 손실
대부분의 경우 AsyncLocalStorage
는 문제 없이 작동합니다. 드물게 비동기 작업 중 하나에서 현재 저장소가 손실될 수 있습니다.
코드가 콜백 기반인 경우 util.promisify()
를 사용하여 Promise화하면 네이티브 Promise와 함께 작동하기 시작합니다.
콜백 기반 API를 사용해야 하거나 코드가 사용자 지정 thenable 구현을 가정하는 경우 AsyncResource
클래스를 사용하여 비동기 작업을 올바른 실행 컨텍스트와 연결합니다. 컨텍스트 손실에 책임이 있는 함수 호출을 찾으려면 손실의 원인이라고 의심되는 호출 후에 asyncLocalStorage.getStore()
의 내용을 로깅합니다. 코드가 undefined
를 로깅하면 마지막으로 호출된 콜백이 컨텍스트 손실의 원인일 가능성이 높습니다.
클래스: AsyncResource
[히스토리]
버전 | 변경 사항 |
---|---|
v16.4.0 | AsyncResource가 이제 Stable되었습니다. 이전에는 Experimental이었습니다. |
AsyncResource
클래스는 임베더의 비동기 리소스에 의해 확장되도록 설계되었습니다. 이를 사용하면 사용자가 자신의 리소스의 수명주기 이벤트를 쉽게 트리거할 수 있습니다.
init
훅은 AsyncResource
가 인스턴스화될 때 트리거됩니다.
다음은 AsyncResource
API에 대한 개요입니다.
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
// AsyncResource()는 확장하기 위한 것입니다. 새로운 AsyncResource()를 인스턴스화하면 init도 트리거됩니다. triggerAsyncId를 생략하면 async_hook.executionAsyncId()가 사용됩니다.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// 리소스의 실행 컨텍스트에서 함수를 실행합니다. 이는 다음을 수행합니다.
// * 리소스의 컨텍스트를 설정합니다.
// * AsyncHooks before 콜백을 트리거합니다.
// * 제공된 함수 `fn`을 제공된 인수로 호출합니다.
// * AsyncHooks after 콜백을 트리거합니다.
// * 원래 실행 컨텍스트를 복원합니다.
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// AsyncHooks destroy 콜백을 호출합니다.
asyncResource.emitDestroy()
// AsyncResource 인스턴스에 할당된 고유 ID를 반환합니다.
asyncResource.asyncId()
// AsyncResource 인스턴스에 대한 트리거 ID를 반환합니다.
asyncResource.triggerAsyncId()
const { AsyncResource, executionAsyncId } = require('node:async_hooks')
// AsyncResource()는 확장하기 위한 것입니다. 새로운 AsyncResource()를 인스턴스화하면 init도 트리거됩니다. triggerAsyncId를 생략하면 async_hook.executionAsyncId()가 사용됩니다.
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// 리소스의 실행 컨텍스트에서 함수를 실행합니다. 이는 다음을 수행합니다.
// * 리소스의 컨텍스트를 설정합니다.
// * AsyncHooks before 콜백을 트리거합니다.
// * 제공된 함수 `fn`을 제공된 인수로 호출합니다.
// * AsyncHooks after 콜백을 트리거합니다.
// * 원래 실행 컨텍스트를 복원합니다.
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// AsyncHooks destroy 콜백을 호출합니다.
asyncResource.emitDestroy()
// AsyncResource 인스턴스에 할당된 고유 ID를 반환합니다.
asyncResource.asyncId()
// AsyncResource 인스턴스에 대한 트리거 ID를 반환합니다.
asyncResource.triggerAsyncId()
new AsyncResource(type[, options])
type
<string> 비동기 이벤트의 유형입니다.options
<Object>triggerAsyncId
<number> 이 비동기 이벤트를 생성한 실행 컨텍스트의 ID입니다. 기본값:executionAsyncId()
.requireManualDestroy
<boolean>true
로 설정하면 객체가 가비지 컬렉션될 때emitDestroy
를 비활성화합니다. 리소스의asyncId
가 검색되고 중요 API의emitDestroy
가 이를 사용하여 호출되지 않는 한 (emitDestroy
가 수동으로 호출되는 경우에도) 일반적으로 설정할 필요가 없습니다.false
로 설정하면 가비지 컬렉션 시emitDestroy
호출은 활성destroy
훅이 하나 이상 있는 경우에만 발생합니다. 기본값:false
.
예시:
class DBQuery extends AsyncResource {
constructor(db) {
super('DBQuery')
this.db = db
}
getInfo(query, callback) {
this.db.get(query, (err, data) => {
this.runInAsyncScope(callback, null, err, data)
})
}
close() {
this.db = null
this.emitDestroy()
}
}
정적 메서드: AsyncResource.bind(fn[, type[, thisArg]])
[히스토리]
버전 | 변경 사항 |
---|---|
v20.0.0 | 바인딩된 함수에 추가된 asyncResource 속성이 더 이상 사용되지 않으며 향후 버전에서 제거될 예정입니다. |
v17.8.0, v16.15.0 | thisArg 가 정의되지 않은 경우 기본값을 호출자의 this 를 사용하도록 변경했습니다. |
v16.0.0 | 선택적 thisArg 추가 |
v14.8.0, v12.19.0 | 추가됨: v14.8.0, v12.19.0 |
fn
<Function> 현재 실행 컨텍스트에 바인딩할 함수입니다.type
<string> 기본AsyncResource
와 연결할 선택적 이름입니다.thisArg
<any>
주어진 함수를 현재 실행 컨텍스트에 바인딩합니다.
asyncResource.bind(fn[, thisArg])
[히스토리]
버전 | 변경 사항 |
---|---|
v20.0.0 | 바인딩된 함수에 추가된 asyncResource 속성은 더 이상 사용되지 않으며 향후 버전에서 제거될 예정입니다. |
v17.8.0, v16.15.0 | thisArg 가 정의되지 않은 경우 기본값이 호출자의 this 를 사용하도록 변경되었습니다. |
v16.0.0 | 선택적 thisArg 추가 |
v14.8.0, v12.19.0 | 추가됨: v14.8.0, v12.19.0 |
fn
<Function> 현재AsyncResource
에 바인딩할 함수입니다.thisArg
<any>
주어진 함수를 이 AsyncResource
의 범위에서 실행하도록 바인딩합니다.
asyncResource.runInAsyncScope(fn[, thisArg, ...args])
추가됨: v9.6.0
fn
<Function> 이 비동기 리소스의 실행 컨텍스트에서 호출할 함수입니다.thisArg
<any> 함수 호출에 사용할 수신기입니다....args
<any> 함수에 전달할 선택적 인수입니다.
비동기 리소스의 실행 컨텍스트에서 제공된 인수를 사용하여 제공된 함수를 호출합니다. 이것은 컨텍스트를 설정하고, 콜백 전에 AsyncHooks를 트리거하고, 함수를 호출하고, 콜백 후에 AsyncHooks를 트리거한 다음 원래 실행 컨텍스트를 복원합니다.
asyncResource.emitDestroy()
- 반환값: <AsyncResource>
asyncResource
에 대한 참조입니다.
모든 destroy
훅을 호출합니다. 이것은 한 번만 호출되어야 합니다. 한 번 이상 호출하면 오류가 발생합니다. 이것은 반드시 수동으로 호출해야 합니다. 리소스가 GC에 의해 수거되도록 남겨두면 destroy
훅은 절대 호출되지 않습니다.
asyncResource.asyncId()
- 반환값: <number> 리소스에 할당된 고유한
asyncId
.
asyncResource.triggerAsyncId()
- 반환값: <number>
AsyncResource
생성자에 전달된 것과 동일한triggerAsyncId
.
Worker
스레드 풀에 대한 AsyncResource
사용
다음 예시는 Worker
풀에 대한 비동기 추적을 적절히 제공하기 위해 AsyncResource
클래스를 사용하는 방법을 보여줍니다. 데이터베이스 연결 풀과 같은 다른 리소스 풀도 유사한 모델을 따를 수 있습니다.
두 개의 숫자를 더하는 작업이라고 가정하고, 다음 내용을 포함하는 task_processor.js
라는 파일을 사용합니다.
import { parentPort } from 'node:worker_threads'
parentPort.on('message', task => {
parentPort.postMessage(task.a + task.b)
})
const { parentPort } = require('node:worker_threads')
parentPort.on('message', task => {
parentPort.postMessage(task.a + task.b)
})
그 주변의 Worker 풀은 다음 구조를 사용할 수 있습니다.
import { AsyncResource } from 'node:async_hooks'
import { EventEmitter } from 'node:events'
import { Worker } from 'node:worker_threads'
const kTaskInfo = Symbol('kTaskInfo')
const kWorkerFreedEvent = Symbol('kWorkerFreedEvent')
class WorkerPoolTaskInfo extends AsyncResource {
constructor(callback) {
super('WorkerPoolTaskInfo')
this.callback = callback
}
done(err, result) {
this.runInAsyncScope(this.callback, null, err, result)
this.emitDestroy() // `TaskInfo`s are used only once.
}
}
export default class WorkerPool extends EventEmitter {
constructor(numThreads) {
super()
this.numThreads = numThreads
this.workers = []
this.freeWorkers = []
this.tasks = []
for (let i = 0; i < numThreads; i++) this.addNewWorker()
// Any time the kWorkerFreedEvent is emitted, dispatch
// the next task pending in the queue, if any.
this.on(kWorkerFreedEvent, () => {
if (this.tasks.length > 0) {
const { task, callback } = this.tasks.shift()
this.runTask(task, callback)
}
})
}
addNewWorker() {
const worker = new Worker(new URL('task_processor.js', import.meta.url))
worker.on('message', result => {
// In case of success: Call the callback that was passed to `runTask`,
// remove the `TaskInfo` associated with the Worker, and mark it as free
// again.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// In case of an uncaught exception: Call the callback that was passed to
// `runTask` with the error.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// Remove the worker from the list and start a new Worker to replace the
// current one.
this.workers.splice(this.workers.indexOf(worker), 1)
this.addNewWorker()
})
this.workers.push(worker)
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
}
runTask(task, callback) {
if (this.freeWorkers.length === 0) {
// No free threads, wait until a worker thread becomes free.
this.tasks.push({ task, callback })
return
}
const worker = this.freeWorkers.pop()
worker[kTaskInfo] = new WorkerPoolTaskInfo(callback)
worker.postMessage(task)
}
close() {
for (const worker of this.workers) worker.terminate()
}
}
const { AsyncResource } = require('node:async_hooks')
const { EventEmitter } = require('node:events')
const path = require('node:path')
const { Worker } = require('node:worker_threads')
const kTaskInfo = Symbol('kTaskInfo')
const kWorkerFreedEvent = Symbol('kWorkerFreedEvent')
class WorkerPoolTaskInfo extends AsyncResource {
constructor(callback) {
super('WorkerPoolTaskInfo')
this.callback = callback
}
done(err, result) {
this.runInAsyncScope(this.callback, null, err, result)
this.emitDestroy() // `TaskInfo`s are used only once.
}
}
class WorkerPool extends EventEmitter {
constructor(numThreads) {
super()
this.numThreads = numThreads
this.workers = []
this.freeWorkers = []
this.tasks = []
for (let i = 0; i < numThreads; i++) this.addNewWorker()
// Any time the kWorkerFreedEvent is emitted, dispatch
// the next task pending in the queue, if any.
this.on(kWorkerFreedEvent, () => {
if (this.tasks.length > 0) {
const { task, callback } = this.tasks.shift()
this.runTask(task, callback)
}
})
}
addNewWorker() {
const worker = new Worker(path.resolve(__dirname, 'task_processor.js'))
worker.on('message', result => {
// In case of success: Call the callback that was passed to `runTask`,
// remove the `TaskInfo` associated with the Worker, and mark it as free
// again.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// In case of an uncaught exception: Call the callback that was passed to
// `runTask` with the error.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// Remove the worker from the list and start a new Worker to replace the
// current one.
this.workers.splice(this.workers.indexOf(worker), 1)
this.addNewWorker()
})
this.workers.push(worker)
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
}
runTask(task, callback) {
if (this.freeWorkers.length === 0) {
// No free threads, wait until a worker thread becomes free.
this.tasks.push({ task, callback })
return
}
const worker = this.freeWorkers.pop()
worker[kTaskInfo] = new WorkerPoolTaskInfo(callback)
worker.postMessage(task)
}
close() {
for (const worker of this.workers) worker.terminate()
}
}
module.exports = WorkerPool
WorkerPoolTaskInfo
객체에 의해 추가된 명시적 추적이 없으면 콜백이 개별 Worker
객체와 연결된 것처럼 보일 것입니다. 그러나 Worker
의 생성은 작업의 생성과 관련이 없으며 작업이 언제 예약되었는지에 대한 정보를 제공하지 않습니다.
이 풀은 다음과 같이 사용할 수 있습니다.
import WorkerPool from './worker_pool.js'
import os from 'node:os'
const pool = new WorkerPool(os.availableParallelism())
let finished = 0
for (let i = 0; i < 10; i++) {
pool.runTask({ a: 42, b: 100 }, (err, result) => {
console.log(i, err, result)
if (++finished === 10) pool.close()
})
}
const WorkerPool = require('./worker_pool.js')
const os = require('node:os')
const pool = new WorkerPool(os.availableParallelism())
let finished = 0
for (let i = 0; i < 10; i++) {
pool.runTask({ a: 42, b: 100 }, (err, result) => {
console.log(i, err, result)
if (++finished === 10) pool.close()
})
}
AsyncResource
와 EventEmitter
통합
EventEmitter
에 의해 트리거되는 이벤트 리스너는 eventEmitter.on()
이 호출되었을 때 활성화되었던 실행 컨텍스트와 다른 실행 컨텍스트에서 실행될 수 있습니다.
다음 예제는 AsyncResource
클래스를 사용하여 이벤트 리스너를 올바른 실행 컨텍스트와 제대로 연결하는 방법을 보여줍니다. 같은 접근 방식을 Stream
또는 유사한 이벤트 기반 클래스에 적용할 수 있습니다.
import { createServer } from 'node:http'
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
const server = createServer((req, res) => {
req.on(
'close',
AsyncResource.bind(() => {
// 실행 컨텍스트는 현재 외부 범위에 바인딩됩니다.
})
)
req.on('close', () => {
// 실행 컨텍스트는 'close' 이벤트 방출을 야기한 범위에 바인딩됩니다.
})
res.end()
}).listen(3000)
const { createServer } = require('node:http')
const { AsyncResource, executionAsyncId } = require('node:async_hooks')
const server = createServer((req, res) => {
req.on(
'close',
AsyncResource.bind(() => {
// 실행 컨텍스트는 현재 외부 범위에 바인딩됩니다.
})
)
req.on('close', () => {
// 실행 컨텍스트는 'close' 이벤트 방출을 야기한 범위에 바인딩됩니다.
})
res.end()
}).listen(3000)