Skip to content

非同期コンテキスト追跡

[安定版: 2 - 安定版]

安定版: 2 安定性: 2 - 安定版

ソースコード: lib/async_hooks.js

はじめに

これらのクラスは、状態を関連付けてコールバックや Promise チェーン全体に伝播するために使用されます。これにより、Web リクエストのライフタイムまたはその他の非同期期間全体にデータを格納できます。他の言語のスレッドローカルストレージに似ています。

AsyncLocalStorageAsyncResourceクラスは、node:async_hooksモジュールの一部です。

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

クラス: AsyncLocalStorage

[履歴]

バージョン変更
v16.4.0AsyncLocalStorage は安定版になりました。以前は実験段階でした。
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')
      // ここで任意の非同期操作のチェーンを想定
      setImmediate(() => {
        logWithId('finish')
        res.end()
      })
    })
  })
  .listen(8080)

http.get('http://localhost:8080')
http.get('http://localhost:8080')
// 出力:
//   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')
      // ここで任意の非同期操作のチェーンを想定
      setImmediate(() => {
        logWithId('finish')
        res.end()
      })
    })
  })
  .listen(8080)

http.get('http://localhost:8080')
http.get('http://localhost:8080')
// 出力:
//   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 <Function> 現在の実行コンテキストにバインドする関数。
  • 戻り値: <Function> キャプチャされた実行コンテキスト内でfnを呼び出す新しい関数。

与えられた関数を現在の実行コンテキストにバインドします。

静的メソッド: AsyncLocalStorage.snapshot()

追加: v19.8.0, v18.16.0

[安定性: 1 - 実験的]

安定性: 1 安定性: 1 - 実験的

  • 戻り値: <Function> シグネチャ(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

[安定版: 1 - 試験段階]

安定版: 1 安定性: 1 - 試験段階

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

[安定版: 1 - 試験段階]

安定版: 1 安定性: 1 - 試験段階

現在の同期実行の残りの期間、コンテキストに移行し、その後、後続の非同期呼び出しを通じてストアを保持します。

例:

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

コンテキスト内で同期的に関数を実行し、その戻り値を返します。ストアはコールバック関数外部からはアクセスできません。ストアは、コールバック内で作成された非同期操作からアクセスできます。

オプションのargsはコールバック関数に渡されます。

コールバック関数がエラーをスローした場合、run()もエラーをスローします。スタックトレースはこの呼び出しの影響を受けず、コンテキストは終了します。

例:

js
const store = { id: 2 }
try {
  asyncLocalStorage.run(store, () => {
    asyncLocalStorage.getStore() // ストアオブジェクトを返します
    setTimeout(() => {
      asyncLocalStorage.getStore() // ストアオブジェクトを返します
    }, 200)
    throw new Error()
  })
} catch (e) {
  asyncLocalStorage.getStore() // undefinedを返します
  // エラーはここでキャッチされます
}

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

追加バージョン: v13.10.0, v12.17.0

[安定版: 1 - 試験的]

安定版: 1 安定性: 1 - 試験的

コンテキスト外で同期的に関数を実行し、その戻り値を返します。ストアは、コールバック関数内またはコールバック内で作成された非同期操作からはアクセスできません。コールバック関数内で実行されるgetStore()呼び出しは常にundefinedを返します。

オプションのargsはコールバック関数に渡されます。

コールバック関数がエラーをスローした場合、exit()もエラーをスローします。スタックトレースはこの呼び出しの影響を受けず、コンテキストは再開されます。

例:

js
// runへの呼び出し内
try {
  asyncLocalStorage.getStore() // ストアオブジェクトまたは値を返します
  asyncLocalStorage.exit(() => {
    asyncLocalStorage.getStore() // undefinedを返します
    throw new Error()
  })
} catch (e) {
  asyncLocalStorage.getStore() // 同じオブジェクトまたは値を返します
  // エラーはここでキャッチされます
}

async/awaitとの使用

非同期関数内で、コンテキスト内で実行されるawait呼び出しが 1 つのみの場合は、次のパターンを使用する必要があります。

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()を使用してプロミス化すれば、ネイティブプロミスで動作し始めます。

コールバックベースの API を使用する必要がある場合、またはコードがカスタムの thenable 実装を想定している場合は、AsyncResourceクラスを使用して、非同期操作を正しい実行コンテキストに関連付ける必要があります。コンテキストの消失の原因となっている関数呼び出しを、疑わしい呼び出しの後にasyncLocalStorage.getStore()の内容をログ出力することで特定します。コードがundefinedを出力する場合、最後に呼び出されたコールバックがコンテキストの消失の原因となっている可能性が高いです。

クラス: AsyncResource

[履歴]

バージョン変更
v16.4.0AsyncResource は安定版になりました。以前は実験的なものでした。

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フックが少なくとも 1 つ存在する場合にのみ行われます。デフォルト: 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 の before コールバックがトリガーされ、関数が呼び出され、AsyncHooks の after コールバックがトリガーされ、その後、元のコンテキストが復元されます。

asyncResource.emitDestroy()

すべてのdestroyフックを呼び出します。これは一度だけ呼び出す必要があります。複数回呼び出すとエラーが発生します。これは必ず手動で呼び出す必要があります。リソースが GC によって収集されるままにすると、destroyフックは決して呼び出されません。

asyncResource.asyncId()

  • 戻り値: <number> リソースに割り当てられた一意のasyncId

asyncResource.triggerAsyncId()

  • 戻り値: <number> AsyncResourceコンストラクタに渡されたのと同じtriggerAsyncId

Worker スレッドプールでの AsyncResource の使用

次の例は、AsyncResourceクラスを使用してWorkerプールのアシンクトラッキングを適切に提供する方法を示しています。データベース接続プールなどの他のリソースプールも同様のモデルに従うことができます。

タスクが 2 つの数値の加算であると仮定し、次の内容の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)
})

それを中心としたワーカープールは、次の構造を使用できます。

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)