Skip to content

비동기 컨텍스트 추적

[Stable: 2 - Stable]

Stable: 2 Stability: 2 - Stable

소스 코드: lib/async_hooks.js

소개

이 클래스들은 상태를 연결하고 콜백과 Promise 체인 전체에 전파하는 데 사용됩니다. 웹 요청 또는 기타 비동기 작업의 수명 동안 데이터를 저장할 수 있습니다. 다른 언어의 스레드 로컬 스토리지와 유사합니다.

AsyncLocalStorageAsyncResource 클래스는 node:async_hooks 모듈의 일부입니다.

js
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
js
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks')

클래스: AsyncLocalStorage

[히스토리]

버전변경 사항
v16.4.0AsyncLocalStorage가 이제 Stable입니다. 이전에는 Experimental이었습니다.
v13.10.0, v12.17.0추가됨: v13.10.0, v12.17.0

이 클래스는 비동기 작업을 통해 일관성을 유지하는 저장소를 만듭니다.

node:async_hooks 모듈을 기반으로 자체 구현을 만들 수 있지만, AsyncLocalStorage는 성능이 뛰어나고 메모리 안전한 구현이며 구현하기 어려운 중요한 최적화를 포함하고 있으므로 선호해야 합니다.

다음 예제에서는 AsyncLocalStorage를 사용하여 들어오는 HTTP 요청에 ID를 할당하고 각 요청 내에서 로깅된 메시지에 포함하는 간단한 로거를 만듭니다.

js
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
js
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.0onPropagate 옵션 제거
v19.2.0, v18.13.0onPropagate 옵션 추가
v13.10.0, v12.17.0추가: v13.10.0, v12.17.0

AsyncLocalStorage의 새 인스턴스를 생성합니다. 저장소는 run() 호출 내부 또는 enterWith() 호출 이후에만 제공됩니다.

정적 메서드: AsyncLocalStorage.bind(fn)

추가: v19.8.0, v18.16.0

[안정성: 1 - 실험적]

안정성: 1 안정성: 1 - 실험적

  • fn <함수> 현재 실행 컨텍스트에 바인딩할 함수
  • 반환값: <함수> 캡처된 실행 컨텍스트 내에서 fn을 호출하는 새 함수

주어진 함수를 현재 실행 컨텍스트에 바인딩합니다.

정적 메서드: AsyncLocalStorage.snapshot()

추가: v19.8.0, v18.16.0

[안정성: 1 - 실험적]

안정성: 1 안정성: 1 - 실험적

  • 반환값: <함수> (fn: (...args) : R, ...args) : R 시그니처를 가진 새 함수

현재 실행 컨텍스트를 캡처하고 인수로 함수를 받는 함수를 반환합니다. 반환된 함수가 호출될 때마다 캡처된 컨텍스트 내에서 전달된 함수를 호출합니다.

js
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 사용을 대체할 수 있습니다. 예를 들어:

js
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

현재 저장소를 반환합니다. asyncLocalStorage.run() 또는 asyncLocalStorage.enterWith()를 호출하여 초기화된 비동기 컨텍스트 외부에서 호출하면 undefined를 반환합니다.

asyncLocalStorage.enterWith(store)

추가됨: v13.11.0, v12.17.0

[Stable: 1 - Experimental]

Stable: 1 Stability: 1 - Experimental

현재 동기 실행의 나머지 기간 동안 컨텍스트로 전환한 다음 후속 비동기 호출을 통해 저장소를 유지합니다.

예시:

js
const store = { id: 1 }
// 주어진 저장소 객체로 이전 저장소를 대체합니다.
asyncLocalStorage.enterWith(store)
asyncLocalStorage.getStore() // 저장소 객체를 반환합니다.
someAsyncOperation(() => {
  asyncLocalStorage.getStore() // 동일한 객체를 반환합니다.
})

이 전환은 전체 동기 실행 동안 계속됩니다. 예를 들어, 이벤트 핸들러 내에서 컨텍스트가 입력되면 후속 이벤트 핸들러도 AsyncResource를 사용하여 다른 컨텍스트에 특별히 바인딩되지 않는 한 해당 컨텍스트 내에서 실행됩니다. 따라서 강력한 이유가 없다면 run()enterWith()보다 선호되어야 합니다.

js
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는 콜백 함수 외부에서는 접근할 수 없습니다. store는 콜백 내에서 생성된 모든 비동기 작업에 접근할 수 있습니다.

선택적 args는 콜백 함수에 전달됩니다.

콜백 함수에서 오류가 발생하면 run()에서도 오류가 발생합니다. 스택 추적은 이 호출의 영향을 받지 않으며 컨텍스트를 종료합니다.

예시:

js
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 - 실험적

컨텍스트 외부에서 함수를 동기적으로 실행하고 그 반환 값을 반환합니다. store는 콜백 함수 또는 콜백 내에서 생성된 비동기 작업 내에서는 접근할 수 없습니다. 콜백 함수 내에서 수행되는 모든 getStore() 호출은 항상 undefined를 반환합니다.

선택적 args는 콜백 함수에 전달됩니다.

콜백 함수에서 오류가 발생하면 exit()에서도 오류가 발생합니다. 스택 추적은 이 호출의 영향을 받지 않으며 컨텍스트로 다시 진입합니다.

예시:

js
// run 호출 내에서
try {
  asyncLocalStorage.getStore() // store 객체 또는 값을 반환합니다
  asyncLocalStorage.exit(() => {
    asyncLocalStorage.getStore() // undefined를 반환합니다
    throw new Error()
  })
} catch (e) {
  asyncLocalStorage.getStore() // 동일한 객체 또는 값을 반환합니다
  // 여기서 오류가 catch됩니다
}

async/await와 함께 사용

비동기 함수 내에서 컨텍스트 내에서 실행될 await 호출이 하나뿐인 경우 다음 패턴을 사용해야 합니다.

js
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.0AsyncResource가 이제 Stable되었습니다. 이전에는 Experimental이었습니다.

AsyncResource 클래스는 임베더의 비동기 리소스에 의해 확장되도록 설계되었습니다. 이를 사용하면 사용자가 자신의 리소스의 수명주기 이벤트를 쉽게 트리거할 수 있습니다.

init 훅은 AsyncResource가 인스턴스화될 때 트리거됩니다.

다음은 AsyncResource API에 대한 개요입니다.

js
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()
js
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.

예시:

js
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.0thisArg가 정의되지 않은 경우 기본값을 호출자의 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.0thisArg가 정의되지 않은 경우 기본값이 호출자의 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()

모든 destroy 훅을 호출합니다. 이것은 한 번만 호출되어야 합니다. 한 번 이상 호출하면 오류가 발생합니다. 이것은 반드시 수동으로 호출해야 합니다. 리소스가 GC에 의해 수거되도록 남겨두면 destroy 훅은 절대 호출되지 않습니다.

asyncResource.asyncId()

  • 반환값: <number> 리소스에 할당된 고유한 asyncId.

asyncResource.triggerAsyncId()

  • 반환값: <number> AsyncResource 생성자에 전달된 것과 동일한 triggerAsyncId.

Worker 스레드 풀에 대한 AsyncResource 사용

다음 예시는 Worker 풀에 대한 비동기 추적을 적절히 제공하기 위해 AsyncResource 클래스를 사용하는 방법을 보여줍니다. 데이터베이스 연결 풀과 같은 다른 리소스 풀도 유사한 모델을 따를 수 있습니다.

두 개의 숫자를 더하는 작업이라고 가정하고, 다음 내용을 포함하는 task_processor.js라는 파일을 사용합니다.

js
import { parentPort } from 'node:worker_threads'
parentPort.on('message', task => {
  parentPort.postMessage(task.a + task.b)
})
js
const { parentPort } = require('node:worker_threads')
parentPort.on('message', task => {
  parentPort.postMessage(task.a + task.b)
})

그 주변의 Worker 풀은 다음 구조를 사용할 수 있습니다.

js
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()
  }
}
js
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의 생성은 작업의 생성과 관련이 없으며 작업이 언제 예약되었는지에 대한 정보를 제공하지 않습니다.

이 풀은 다음과 같이 사용할 수 있습니다.

js
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()
  })
}
js
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()
  })
}

AsyncResourceEventEmitter 통합

EventEmitter에 의해 트리거되는 이벤트 리스너는 eventEmitter.on()이 호출되었을 때 활성화되었던 실행 컨텍스트와 다른 실행 컨텍스트에서 실행될 수 있습니다.

다음 예제는 AsyncResource 클래스를 사용하여 이벤트 리스너를 올바른 실행 컨텍스트와 제대로 연결하는 방법을 보여줍니다. 같은 접근 방식을 Stream 또는 유사한 이벤트 기반 클래스에 적용할 수 있습니다.

js
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)
js
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)