Skip to content

Tracciamento del contesto asincrono

[Stabile: 2 - Stabile]

Stabile: 2 Stabilità: 2 - Stabile

Codice Sorgente: lib/async_hooks.js

Introduzione

Queste classi vengono utilizzate per associare lo stato e propagarlo attraverso callback e catene di promesse. Consentono di memorizzare i dati per tutta la durata di una richiesta web o di qualsiasi altra durata asincrona. È simile allo storage locale dei thread in altri linguaggi.

Le classi AsyncLocalStorage e AsyncResource fanno parte del modulo node:async_hooks:

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

Classe: AsyncLocalStorage

[Cronologia]

VersioneCambiamenti
v16.4.0AsyncLocalStorage ora è Stabile. In precedenza era Sperimentale.
v13.10.0, v12.17.0Aggiunto in: v13.10.0, v12.17.0

Questa classe crea store che rimangono coerenti attraverso le operazioni asincrone.

Sebbene sia possibile creare una propria implementazione sopra il modulo node:async_hooks, è preferibile AsyncLocalStorage in quanto è un'implementazione performante e sicura per la memoria che comporta ottimizzazioni significative non ovvie da implementare.

L'esempio seguente utilizza AsyncLocalStorage per creare un semplice logger che assegna ID alle richieste HTTP in entrata e li include nei messaggi registrati all'interno di ciascuna richiesta.

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')
      // Immagina una qualsiasi catena di operazioni asincrone qui
      setImmediate(() => {
        logWithId('finish')
        res.end()
      })
    })
  })
  .listen(8080)

http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Output:
//   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')
      // Immagina una qualsiasi catena di operazioni asincrone qui
      setImmediate(() => {
        logWithId('finish')
        res.end()
      })
    })
  })
  .listen(8080)

http.get('http://localhost:8080')
http.get('http://localhost:8080')
// Output:
//   0: start
//   1: start
//   0: finish
//   1: finish

Ogni istanza di AsyncLocalStorage mantiene un contesto di storage indipendente. Più istanze possono esistere in modo sicuro contemporaneamente senza rischio di interferire con i dati dell'altra.

new AsyncLocalStorage()

[Cronologia]

VersioneModifiche
v19.7.0, v18.16.0Rimossa l'opzione sperimentale onPropagate.
v19.2.0, v18.13.0Aggiunta l'opzione onPropagate.
v13.10.0, v12.17.0Aggiunto in: v13.10.0, v12.17.0

Crea una nuova istanza di AsyncLocalStorage. Lo store è disponibile solo all'interno di una chiamata run() o dopo una chiamata enterWith().

Metodo statico: AsyncLocalStorage.bind(fn)

Aggiunto in: v19.8.0, v18.16.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

  • fn <Funzione> La funzione da collegare al contesto di esecuzione corrente.
  • Restituisce: <Funzione> Una nuova funzione che chiama fn all'interno del contesto di esecuzione acquisito.

Collega la funzione specificata al contesto di esecuzione corrente.

Metodo statico: AsyncLocalStorage.snapshot()

Aggiunto in: v19.8.0, v18.16.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

  • Restituisce: <Funzione> Una nuova funzione con la firma (fn: (...args) : R, ...args) : R.

Cattura il contesto di esecuzione corrente e restituisce una funzione che accetta una funzione come argomento. Ogni volta che viene chiamata la funzione restituita, chiama la funzione passata al suo interno nel contesto acquisito.

js
const asyncLocalStorage = new AsyncLocalStorage()
const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot())
const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()))
console.log(result) // restituisce 123

AsyncLocalStorage.snapshot() può sostituire l'uso di AsyncResource per semplici scopi di tracciamento del contesto asincrono, ad esempio:

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())) // restituisce 123

asyncLocalStorage.disable()

Aggiunto in: v13.10.0, v12.17.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

Disabilita l'istanza di AsyncLocalStorage. Tutte le chiamate successive a asyncLocalStorage.getStore() restituiranno undefined finché asyncLocalStorage.run() o asyncLocalStorage.enterWith() non verranno richiamate.

Quando si chiama asyncLocalStorage.disable(), tutti i contesti correnti collegati all'istanza verranno terminati.

La chiamata a asyncLocalStorage.disable() è necessaria prima che asyncLocalStorage possa essere sottoposto a garbage collection. Ciò non si applica agli archivi forniti da asyncLocalStorage, poiché tali oggetti vengono sottoposti a garbage collection insieme alle risorse asincrone corrispondenti.

Utilizzare questo metodo quando asyncLocalStorage non è più in uso nel processo corrente.

asyncLocalStorage.getStore()

Aggiunto in: v13.10.0, v12.17.0

Restituisce l'archivio corrente. Se chiamato al di fuori di un contesto asincrono inizializzato chiamando asyncLocalStorage.run() o asyncLocalStorage.enterWith(), restituisce undefined.

asyncLocalStorage.enterWith(store)

Aggiunto in: v13.11.0, v12.17.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

Passa al contesto per il resto dell'esecuzione sincrona corrente e quindi rende persistente l'archivio tramite qualsiasi successiva chiamata asincrona.

Esempio:

js
const store = { id: 1 }
// Sostituisce l'archivio precedente con l'oggetto archivio specificato
asyncLocalStorage.enterWith(store)
asyncLocalStorage.getStore() // Restituisce l'oggetto archivio
someAsyncOperation(() => {
  asyncLocalStorage.getStore() // Restituisce lo stesso oggetto
})

Questa transizione continuerà per l'intera esecuzione sincrona. Ciò significa che se, ad esempio, si accede al contesto all'interno di un gestore di eventi, anche i successivi gestori di eventi verranno eseguiti all'interno di tale contesto a meno che non siano specificamente associati a un altro contesto con un AsyncResource. Ecco perché run() dovrebbe essere preferito a enterWith() a meno che non ci siano valide ragioni per utilizzare quest'ultimo metodo.

js
const store = { id: 1 }

emitter.on('my-event', () => {
  asyncLocalStorage.enterWith(store)
})
emitter.on('my-event', () => {
  asyncLocalStorage.getStore() // Restituisce lo stesso oggetto
})

asyncLocalStorage.getStore() // Restituisce undefined
emitter.emit('my-event')
asyncLocalStorage.getStore() // Restituisce lo stesso oggetto

asyncLocalStorage.run(store, callback[, ...args])

Aggiunto in: v13.10.0, v12.17.0

Esegue una funzione in modo sincrono all'interno di un contesto e restituisce il suo valore di ritorno. L'archivio non è accessibile al di fuori della funzione di callback. L'archivio è accessibile a qualsiasi operazione asincrona creata all'interno della callback.

Gli args opzionali vengono passati alla funzione di callback.

Se la funzione di callback genera un errore, l'errore viene generato anche da run(). La stacktrace non è influenzata da questa chiamata e il contesto viene abbandonato.

Esempio:

js
const store = { id: 2 }
try {
  asyncLocalStorage.run(store, () => {
    asyncLocalStorage.getStore() // Restituisce l'oggetto store
    setTimeout(() => {
      asyncLocalStorage.getStore() // Restituisce l'oggetto store
    }, 200)
    throw new Error()
  })
} catch (e) {
  asyncLocalStorage.getStore() // Restituisce undefined
  // L'errore verrà catturato qui
}

asyncLocalStorage.exit(callback[, ...args])

Aggiunto in: v13.10.0, v12.17.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

Esegue una funzione in modo sincrono al di fuori di un contesto e restituisce il suo valore di ritorno. L'archivio non è accessibile all'interno della funzione di callback o delle operazioni asincrone create all'interno della callback. Qualsiasi chiamata getStore() effettuata all'interno della funzione di callback restituirà sempre undefined.

Gli args opzionali vengono passati alla funzione di callback.

Se la funzione di callback genera un errore, l'errore viene generato anche da exit(). La stacktrace non è influenzata da questa chiamata e il contesto viene ripristinato.

Esempio:

js
// All'interno di una chiamata a run
try {
  asyncLocalStorage.getStore() // Restituisce l'oggetto o il valore dell'archivio
  asyncLocalStorage.exit(() => {
    asyncLocalStorage.getStore() // Restituisce undefined
    throw new Error()
  })
} catch (e) {
  asyncLocalStorage.getStore() // Restituisce lo stesso oggetto o valore
  // L'errore verrà catturato qui
}

Utilizzo con async/await

Se, all'interno di una funzione asincrona, è prevista l'esecuzione di una sola chiamata await in un contesto, è necessario utilizzare il seguente schema:

js
async function fn() {
  await asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('key', value)
    return foo() // Il valore restituito da foo sarà sottoposto a await
  })
}

In questo esempio, l'archivio è disponibile solo nella funzione di callback e nelle funzioni chiamate da foo. Al di fuori di run, la chiamata a getStore restituirà undefined.

Risoluzione dei problemi: perdita di contesto

Nella maggior parte dei casi, AsyncLocalStorage funziona senza problemi. In rare situazioni, l'archivio corrente viene perso in una delle operazioni asincrone.

Se il codice è basato su callback, è sufficiente trasformarlo in una promessa con util.promisify() in modo che inizi a funzionare con le promesse native.

Se è necessario utilizzare un'API basata su callback o il codice presuppone un'implementazione thenable personalizzata, utilizzare la classe AsyncResource per associare l'operazione asincrona al contesto di esecuzione corretto. Individuare la chiamata di funzione responsabile della perdita di contesto registrando il contenuto di asyncLocalStorage.getStore() dopo le chiamate che si sospetta siano responsabili della perdita. Quando il codice registra undefined, l'ultima callback chiamata è probabilmente responsabile della perdita di contesto.

Classe: AsyncResource

[Cronologia]

VersioneModifiche
v16.4.0AsyncResource è ora Stabile. In precedenza, era Sperimentale.

La classe AsyncResource è progettata per essere estesa dalle risorse asincrone dell'embedder. In questo modo, gli utenti possono attivare facilmente gli eventi di durata delle proprie risorse.

L'hook init viene attivato quando viene creata un'istanza di AsyncResource.

Di seguito è riportata una panoramica dell'API AsyncResource.

js
import { AsyncResource, executionAsyncId } from 'node:async_hooks'

// AsyncResource() è pensato per essere esteso. La creazione di una
// nuova istanza di AsyncResource() attiva anche init. Se triggerAsyncId viene omesso, allora
// viene utilizzato async_hook.executionAsyncId().
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })

// Esegui una funzione nel contesto di esecuzione della risorsa. Questo
// * stabilirà il contesto della risorsa
// * attiverà le callback AsyncHooks before
// * chiamerà la funzione fornita `fn` con gli argomenti forniti
// * attiverà le callback AsyncHooks after
// * ripristinerà il contesto di esecuzione originale
asyncResource.runInAsyncScope(fn, thisArg, ...args)

// Chiama le callback di distruzione di AsyncHooks.
asyncResource.emitDestroy()

// Restituisce l'ID univoco assegnato all'istanza di AsyncResource.
asyncResource.asyncId()

// Restituisce l'ID trigger per l'istanza di AsyncResource.
asyncResource.triggerAsyncId()
js
const { AsyncResource, executionAsyncId } = require('node:async_hooks')

// AsyncResource() è pensato per essere esteso. La creazione di una
// nuova istanza di AsyncResource() attiva anche init. Se triggerAsyncId viene omesso, allora
// viene utilizzato async_hook.executionAsyncId().
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })

// Esegui una funzione nel contesto di esecuzione della risorsa. Questo
// * stabilirà il contesto della risorsa
// * attiverà le callback AsyncHooks before
// * chiamerà la funzione fornita `fn` con gli argomenti forniti
// * attiverà le callback AsyncHooks after
// * ripristinerà il contesto di esecuzione originale
asyncResource.runInAsyncScope(fn, thisArg, ...args)

// Chiama le callback di distruzione di AsyncHooks.
asyncResource.emitDestroy()

// Restituisce l'ID univoco assegnato all'istanza di AsyncResource.
asyncResource.asyncId()

// Restituisce l'ID trigger per l'istanza di AsyncResource.
asyncResource.triggerAsyncId()

new AsyncResource(type[, options])

  • type <string> Il tipo di evento asincrono.
  • options <Object>
    • triggerAsyncId <number> L'ID del contesto di esecuzione che ha creato questo evento asincrono. Predefinito: executionAsyncId().
    • requireManualDestroy <boolean> Se impostato su true, disabilita emitDestroy quando l'oggetto viene eliminato dal Garbage Collector. Di solito non è necessario impostarlo (anche se emitDestroy viene chiamato manualmente), a meno che l'asyncId della risorsa non venga recuperato e l'emitDestroy dell'API sensibile non venga chiamato con esso. Quando impostato su false, la chiamata emitDestroy durante l'eliminazione da parte del Garbage Collector avverrà solo se è presente almeno un hook destroy attivo. Predefinito: false.

Esempio di utilizzo:

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

Metodo statico: AsyncResource.bind(fn[, type[, thisArg]])

[Cronologia]

VersioneModifiche
v20.0.0La proprietà asyncResource aggiunta alla funzione associata è stata deprecata e verrà rimossa in una versione futura.
v17.8.0, v16.15.0Modificato il valore predefinito quando thisArg è indefinito per utilizzare this dal chiamante.
v16.0.0Aggiunto thisArg opzionale.
v14.8.0, v12.19.0Aggiunto in: v14.8.0, v12.19.0
  • fn <Function> La funzione da associare al contesto di esecuzione corrente.
  • type <string> Un nome opzionale da associare all'AsyncResource sottostante.
  • thisArg <any>

Associa la funzione data al contesto di esecuzione corrente.

asyncResource.bind(fn[, thisArg])

[Cronologia]

VersioneModifiche
v20.0.0La proprietà asyncResource aggiunta alla funzione associata è stata deprecata e verrà rimossa in una versione futura.
v17.8.0, v16.15.0Modificato il valore predefinito quando thisArg è indefinito per usare this dal chiamante.
v16.0.0Aggiunto thisArg opzionale.
v14.8.0, v12.19.0Aggiunto in: v14.8.0, v12.19.0
  • fn <Function> La funzione da associare all'attuale AsyncResource.
  • thisArg <any>

Associa la funzione specificata per essere eseguita nell'ambito di questo AsyncResource.

asyncResource.runInAsyncScope(fn[, thisArg, ...args])

Aggiunto in: v9.6.0

  • fn <Function> La funzione da chiamare nel contesto di esecuzione di questa risorsa asincrona.
  • thisArg <any> Il ricevitore da utilizzare per la chiamata di funzione.
  • ...args <any> Argomenti opzionali da passare alla funzione.

Chiama la funzione fornita con gli argomenti forniti nel contesto di esecuzione della risorsa asincrona. Ciò stabilirà il contesto, attiverà i callback AsyncHooks before, chiamerà la funzione, attiverà i callback AsyncHooks after e quindi ripristinerà il contesto di esecuzione originale.

asyncResource.emitDestroy()

Chiama tutti gli hook destroy. Questo dovrebbe essere chiamato solo una volta. Verrà generato un errore se viene chiamato più di una volta. Questo deve essere chiamato manualmente. Se la risorsa viene lasciata alla raccolta del GC, gli hook destroy non verranno mai chiamati.

asyncResource.asyncId()

  • Restituisce: <number> L'asyncId univoco assegnato alla risorsa.

asyncResource.triggerAsyncId()

  • Restituisce: <number> Lo stesso triggerAsyncId che viene passato al costruttore AsyncResource.

Utilizzo di AsyncResource per un pool di thread Worker

L'esempio seguente mostra come utilizzare la classe AsyncResource per fornire correttamente il tracciamento asincrono per un pool Worker. Altri pool di risorse, come i pool di connessioni al database, possono seguire un modello simile.

Supponendo che l'attività sia l'aggiunta di due numeri, utilizzando un file denominato task_processor.js con il seguente contenuto:

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

Un pool di Worker attorno ad esso potrebbe utilizzare la seguente struttura:

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` vengono utilizzati una sola volta.
  }
}

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

    // Ogni volta che viene emesso l'evento kWorkerFreedEvent, invia
    // l'attività successiva in sospeso nella coda, se presente.
    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 caso di successo: chiama la callback che è stata passata a `runTask`,
      // rimuovi le `TaskInfo` associate al Worker e contrassegnale di nuovo come libere.
      worker[kTaskInfo].done(null, result)
      worker[kTaskInfo] = null
      this.freeWorkers.push(worker)
      this.emit(kWorkerFreedEvent)
    })
    worker.on('error', err => {
      // In caso di eccezione non gestita: chiama la callback che è stata passata a
      // `runTask` con l'errore.
      if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
      else this.emit('error', err)
      // Rimuovi il worker dall'elenco e avvia un nuovo Worker per sostituire quello
      // corrente.
      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) {
      // Nessun thread libero, attendi finché un thread worker non diventa libero.
      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` vengono utilizzati una sola volta.
  }
}

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

    // Ogni volta che viene emesso l'evento kWorkerFreedEvent, invia
    // l'attività successiva in sospeso nella coda, se presente.
    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 caso di successo: chiama la callback che è stata passata a `runTask`,
      // rimuovi le `TaskInfo` associate al Worker e contrassegnale di nuovo come libere.
      worker[kTaskInfo].done(null, result)
      worker[kTaskInfo] = null
      this.freeWorkers.push(worker)
      this.emit(kWorkerFreedEvent)
    })
    worker.on('error', err => {
      // In caso di eccezione non gestita: chiama la callback che è stata passata a
      // `runTask` con l'errore.
      if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
      else this.emit('error', err)
      // Rimuovi il worker dall'elenco e avvia un nuovo Worker per sostituire quello
      // corrente.
      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) {
      // Nessun thread libero, attendi finché un thread worker non diventa libero.
      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

Senza il tracciamento esplicito aggiunto dagli oggetti WorkerPoolTaskInfo, sembrerebbe che le callback siano associate ai singoli oggetti Worker. Tuttavia, la creazione dei Worker non è associata alla creazione delle attività e non fornisce informazioni su quando le attività sono state pianificate.

Questo pool potrebbe essere utilizzato come segue:

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

Integrazione di AsyncResource con EventEmitter

I listener di eventi attivati da un EventEmitter possono essere eseguiti in un contesto di esecuzione diverso da quello attivo quando è stato chiamato eventEmitter.on().

L'esempio seguente mostra come utilizzare la classe AsyncResource per associare correttamente un listener di eventi con il contesto di esecuzione corretto. Lo stesso approccio può essere applicato a uno Stream o a una classe simile basata su eventi.

js
import { createServer } from 'node:http'
import { AsyncResource, executionAsyncId } from 'node:async_hooks'

const server = createServer((req, res) => {
  req.on(
    'close',
    AsyncResource.bind(() => {
      // Il contesto di esecuzione è associato all'ambito esterno corrente.
    })
  )
  req.on('close', () => {
    // Il contesto di esecuzione è associato all'ambito che ha causato l'emissione di '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(() => {
      // Il contesto di esecuzione è associato all'ambito esterno corrente.
    })
  )
  req.on('close', () => {
    // Il contesto di esecuzione è associato all'ambito che ha causato l'emissione di 'close'.
  })
  res.end()
}).listen(3000)