تتبع السياق غير المتزامن
[مستقر: 2 - مستقر]
مستقر: 2 استقرار: 2 - مستقر
رمز المصدر: lib/async_hooks.js
مقدمة
تُستخدم هذه الفئات لربط الحالة ونشرها في جميع عمليات الاستدعاء الراجعة وسلاسل الوعود. تتيح تخزين البيانات طوال عمر طلب ويب أو أي مدة غير متزامنة أخرى. وهي مشابهة لتخزين محلي للخيوط في لغات أخرى.
تُعد فئتا AsyncLocalStorage
و AsyncResource
جزءًا من وحدة node:async_hooks
:
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks')
الفئة: AsyncLocalStorage
[السجل]
الإصدار | التغييرات |
---|---|
v16.4.0 | أصبح AsyncLocalStorage الآن مستقرًا. كان تجريبيًا سابقًا. |
v13.10.0، v12.17.0 | تمت الإضافة في: v13.10.0، v12.17.0 |
تقوم هذه الفئة بإنشاء مخازن تبقى متماسكة عبر العمليات غير المتزامنة.
بينما يمكنك إنشاء تنفيذك الخاص أعلى وحدة node:async_hooks
، يجب تفضيل AsyncLocalStorage
لأنه تنفيذ عالي الأداء وآمن للذاكرة ينطوي على تحسينات كبيرة يصعب تنفيذها.
يوضح المثال التالي استخدام AsyncLocalStorage
لإنشاء مسجل بسيط يعين معرفات لطلبات HTTP الواردة ويتضمنها في الرسائل المسجلة ضمن كل طلب.
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
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.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
fn
<Function> الدالة التي سيتم ربطها بسياق التنفيذ الحالي.- الإرجاع: <Function> دالة جديدة تستدعي
fn
ضمن سياق التنفيذ المُلتقط.
يربط الدالة المعطاة بسياق التنفيذ الحالي.
طريقة ثابتة: AsyncLocalStorage.snapshot()
تمت الإضافة في: v19.8.0، v18.16.0
- الإرجاع: <Function> دالة جديدة ذات توقيع
(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
[مستقر: 1 - تجريبي]
مستقر: 1 استقرار: 1 - تجريبي
يعطل مثيل AsyncLocalStorage
. ستعيد جميع المكالمات اللاحقة إلى asyncLocalStorage.getStore()
قيمة undefined
حتى يتم استدعاء asyncLocalStorage.run()
أو asyncLocalStorage.enterWith()
مرة أخرى.
عند استدعاء asyncLocalStorage.disable()
, سيتم الخروج من جميع السياقات الحالية المرتبطة بالمثيل.
يُطلب استدعاء asyncLocalStorage.disable()
قبل أن يتمكن asyncLocalStorage
من التجميع بواسطة جامع القمامة. لا ينطبق هذا على المخازن التي يوفرها 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 - تجريبي
store
<أي>
ينتقل إلى السياق لبقية التنفيذ المتزامن الحالي ثم يُبقي المخزن عبر أي مكالمات غير متزامنة لاحقة.
مثال:
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>
يقوم بتشغيل دالة بشكل متزامن داخل سياق ويعيد قيمة إرجاعها. لا يمكن الوصول إلى المخزن خارج دالة الاستدعاء. يمكن الوصول إلى المخزن من قبل أي عمليات غير متزامنة تم إنشاؤها داخل دالة الاستدعاء.
يتم تمرير الوسائط الاختيارية args
إلى دالة الاستدعاء.
إذا قامت دالة الاستدعاء بإلقاء خطأ، فسيتم إلقاء الخطأ بواسطة run()
أيضًا. لا يتأثر تتبع المكدس بهذه المكالمة ويتم الخروج من السياق.
مثال:
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
callback
<Function>...args
<any>
يقوم بتشغيل دالة بشكل متزامن خارج سياق ويعيد قيمة إرجاعها. لا يمكن الوصول إلى المخزن داخل دالة الاستدعاء أو العمليات غير المتزامنة التي تم إنشاؤها داخل دالة الاستدعاء. أي مكالمة getStore()
تتم داخل دالة الاستدعاء ستعيد دائمًا undefined
.
يتم تمرير الوسائط الاختيارية args
إلى دالة الاستدعاء.
إذا قامت دالة الاستدعاء بإلقاء خطأ، فسيتم إلقاء الخطأ بواسطة exit()
أيضًا. لا يتأثر تتبع المكدس بهذه المكالمة ويتم إعادة الدخول إلى السياق.
مثال:
// ضمن مكالمة إلى run
try {
asyncLocalStorage.getStore() // يعيد كائن المخزن أو القيمة
asyncLocalStorage.exit(() => {
asyncLocalStorage.getStore() // يعيد undefined
throw new Error()
})
} catch (e) {
asyncLocalStorage.getStore() // يعيد نفس الكائن أو القيمة
// سيتم التقاط الخطأ هنا
}
الاستخدام مع async/await
إذا كان داخل دالة async
، يجب تشغيل await
مرة واحدة فقط داخل سياق ما، فيجب استخدام النمط التالي:
async function fn() {
await asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('key', value)
return foo() // سيتم انتظار قيمة الإرجاع من foo
})
}
في هذا المثال، المخزن متوفر فقط داخل دالة الارتجاع والدوال التي تدعوها foo
. خارج run
، فإن استدعاء getStore
سيرجع undefined
.
استكشاف الأخطاء وإصلاحها: فقدان السياق
في معظم الحالات، يعمل AsyncLocalStorage
بدون مشاكل. في حالات نادرة، يضيع المخزن الحالي في إحدى العمليات غير المتزامنة.
إذا كان كودك قائمًا على الارتجاع، يكفي تحويله إلى وعود باستخدام util.promisify()
حتى يبدأ العمل مع الوعود الأصلية.
إذا كنت بحاجة إلى استخدام واجهة برمجة تطبيقات قائمة على الارتجاع أو افترض كودك تنفيذًا قابلًا للتنفيذ مخصصًا، فاستخدم فئة AsyncResource
لربط العملية غير المتزامنة بسياق التنفيذ الصحيح. ابحث عن دعوة الدالة المسؤولة عن فقدان السياق عن طريق تسجيل محتوى asyncLocalStorage.getStore()
بعد الدعوات التي تشك في أنها مسؤولة عن الخسارة. عندما يسجل الكود undefined
، فإن آخر دالة ارتجاع تم استدعاؤها هي المسؤولة على الأرجح عن فقدان السياق.
الفئة: AsyncResource
[السجل]
الإصدار | التغييرات |
---|---|
v16.4.0 | أصبح AsyncResource الآن مستقرًا. كان تجريبيًا سابقًا. |
تم تصميم فئة AsyncResource
لتوسيعها بواسطة موارد المُدمِج غير المتزامنة. باستخدام هذا، يمكن للمستخدمين بسهولة تشغيل أحداث دورة حياة مواردهم الخاصة.
سيتم تشغيل خطاف init
عند إنشاء AsyncResource
.
فيما يلي نظرة عامة على واجهة برمجة تطبيقات AsyncResource
.
import { AsyncResource, executionAsyncId } from 'node:async_hooks'
// AsyncResource() يُقصد بها أن تُوسّع. إنشاء
// AsyncResource() جديد يُشغّل أيضًا init. إذا تم حذف triggerAsyncId، فسيتم استخدام
// async_hook.executionAsyncId().
const asyncResource = new AsyncResource(type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false })
// تشغيل دالة في سياق تنفيذ المورد. سيفعل هذا
// * إنشاء سياق المورد
// * تشغيل AsyncHooks قبل عمليات الارتجاع
// * استدعاء الدالة المُقدّمة `fn` مع الوسائط المُقدّمة
// * تشغيل AsyncHooks بعد عمليات الارتجاع
// * استعادة سياق التنفيذ الأصلي
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// استدعاء عمليات ارتجاع AsyncHooks destroy.
asyncResource.emitDestroy()
// إرجاع المعرف الفريد المُعيّن لمثيل AsyncResource.
asyncResource.asyncId()
// إرجاع معرف المُشغّل لمثيل AsyncResource.
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 قبل عمليات الارتجاع
// * استدعاء الدالة المُقدّمة `fn` مع الوسائط المُقدّمة
// * تشغيل AsyncHooks بعد عمليات الارتجاع
// * استعادة سياق التنفيذ الأصلي
asyncResource.runInAsyncScope(fn, thisArg, ...args)
// استدعاء عمليات ارتجاع AsyncHooks destroy.
asyncResource.emitDestroy()
// إرجاع المعرف الفريد المُعيّن لمثيل AsyncResource.
asyncResource.asyncId()
// إرجاع معرف المُشغّل لمثيل AsyncResource.
asyncResource.triggerAsyncId()
new AsyncResource(type[, options])
type
<string> نوع الحدث غير المتزامن.options
<Object>triggerAsyncId
<number> معرف سياق التنفيذ الذي أنشأ هذا الحدث غير المتزامن. افتراضيًا:executionAsyncId()
.requireManualDestroy
<boolean> إذا تم تعيينه علىtrue
، فإنه يُعطلemitDestroy
عند جمع البيانات المهملة للكائن. وعادةً ما لا يلزم تعيين هذا (حتى إذا تم استدعاءemitDestroy
يدويًا)، إلا إذا تم استردادasyncId
الخاص بالمورد وتم استدعاء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> الدالة التي سيتم استدعاؤها في سياق تنفيذ موردasync
هذا.thisArg
<any> المُستقبِل الذي سيتم استخدامه لاستدعاء الدالة....args
<any> وسيطات اختيارية لإرسالها إلى الدالة.
استدعِ الدالة المُقدّمة مع الوسيطات المُقدّمة في سياق تنفيذ مورد async
. سيؤدي هذا إلى إنشاء السياق، وتشغيل AsyncHooks قبل المُستدعيات، واستدعاء الدالة، وتشغيل AsyncHooks بعد المُستدعيات، ثم استعادة سياق التنفيذ الأصلي.
asyncResource.emitDestroy()
- القيمة المُرجعة: <AsyncResource> مرجع إلى
asyncResource
.
استدعِ جميع مُعلّقات destroy
. يجب أن يتم استدعاء هذا مرة واحدة فقط. سيتم إرسال خطأ إذا تم استدعاؤه أكثر من مرة. يجب إجراء هذا يدويًا. إذا تم ترك المورد ليتم جمعه بواسطة GC، فلن يتم استدعاء مُعلّقات destroy
أبدًا.
asyncResource.asyncId()
- الإرجاع: <number>
asyncId
الفريد المُعيّن للمورد.
asyncResource.triggerAsyncId()
- الإرجاع: <number> نفس
triggerAsyncId
الذي يتم تمريره إلى مُنشئAsyncResource
.
استخدام AsyncResource
لبركة خيوط Worker
يُظهر المثال التالي كيفية استخدام فئة AsyncResource
لتوفير تتبع غير متزامن بشكل صحيح لبركة Worker
. يمكن لبرك الموارد الأخرى، مثل برك اتصال قواعد البيانات، اتباع نموذج مشابه.
بافتراض أن المهمة هي إضافة رقمين، باستخدام ملف اسمه 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)
})
يمكن لبركة عامل حولها استخدام البنية التالية:
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()
// في كل مرة يتم فيها إصدار kWorkerFreedEvent، قم بتوزيع
// المهمة التالية المعلقة في قائمة الانتظار، إن وجدت.
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 => {
// في حالة النجاح: اتصل بالدالة المُرجعة التي تم تمريرها إلى `runTask`،
// قم بإزالة `TaskInfo` المرتبط بـ Worker، وعلامته على أنه مجاني
// مرة أخرى.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// في حالة استثناء غير معالج: اتصل بالدالة المُرجعة التي تم تمريرها إلى
// `runTask` مع الخطأ.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// قم بإزالة العامل من القائمة وابدأ عاملًا جديدًا ليحل محل
// العامل الحالي.
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) {
// لا توجد خيوط مجانية، انتظر حتى يصبح خيط عامل مجانيًا.
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()
// في كل مرة يتم فيها إصدار kWorkerFreedEvent، قم بتوزيع
// المهمة التالية المعلقة في قائمة الانتظار، إن وجدت.
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 => {
// في حالة النجاح: اتصل بالدالة المُرجعة التي تم تمريرها إلى `runTask`،
// قم بإزالة `TaskInfo` المرتبط بـ Worker، وعلامته على أنه مجاني
// مرة أخرى.
worker[kTaskInfo].done(null, result)
worker[kTaskInfo] = null
this.freeWorkers.push(worker)
this.emit(kWorkerFreedEvent)
})
worker.on('error', err => {
// في حالة استثناء غير معالج: اتصل بالدالة المُرجعة التي تم تمريرها إلى
// `runTask` مع الخطأ.
if (worker[kTaskInfo]) worker[kTaskInfo].done(err, null)
else this.emit('error', err)
// قم بإزالة العامل من القائمة وابدأ عاملًا جديدًا ليحل محل
// العامل الحالي.
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) {
// لا توجد خيوط مجانية، انتظر حتى يصبح خيط عامل مجانيًا.
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)