خطافات غير متزامنة
[مستقر: 1 - تجريبي]
مستقر: 1 استقرار: 1 - تجريبي. يُرجى الانتقال بعيدًا عن واجهة برمجة التطبيقات هذه، إذا أمكن. لا نوصي باستخدام واجهات برمجة التطبيقات createHook
، وAsyncHook
، وexecutionAsyncResource
نظرًا لأنها تعاني من مشكلات في إمكانية الاستخدام، ومخاطر أمنية، وآثار على الأداء. يتم التعامل مع حالات استخدام تتبع السياق غير المتزامن بشكل أفضل بواسطة واجهة برمجة التطبيقات المستقرة AsyncLocalStorage
. إذا كان لديك حالة استخدام لـcreateHook
، أو AsyncHook
، أو executionAsyncResource
تتجاوز حاجة تتبع السياق التي تحلها AsyncLocalStorage
أو بيانات التشخيص التي توفرها حاليًا قناة التشخيص، فيرجى فتح مشكلة في https://github.com/nodejs/node/issues تصف حالة الاستخدام الخاصة بك حتى نتمكن من إنشاء واجهة برمجة تطبيقات أكثر تركيزًا على الغرض.
شفرة المصدر: lib/async_hooks.js
ننصح بشدة بعدم استخدام واجهة برمجة التطبيقات async_hooks
. تتضمن واجهات برمجة التطبيقات الأخرى التي يمكنها تغطية معظم حالات استخدامها ما يلي:
AsyncLocalStorage
تتبع السياق غير المتزامنprocess.getActiveResourcesInfo()
تتبع الموارد النشطة
توفر وحدة node:async_hooks
واجهة برمجة تطبيقات لتتبع الموارد غير المتزامنة. يمكن الوصول إليها باستخدام:
import async_hooks from 'node:async_hooks'
const async_hooks = require('node:async_hooks')
المصطلحات
يمثل المورد غير المتزامن كائنًا به دالة استدعاء مرتبطة. قد يتم استدعاء هذه الدالة الاستدعاء عدة مرات، مثل حدث 'connection'
في net.createServer()
، أو مرة واحدة فقط كما في fs.open()
. يمكن أيضًا إغلاق المورد قبل استدعاء الدالة الاستدعاء. لا يميز AsyncHook
صراحةً بين هذه الحالات المختلفة، ولكنه سيمثلها كمفهوم مجرد وهو مورد.
إذا تم استخدام Worker
، فإن كل مؤشر ترابط يحتوي على واجهة async_hooks
مستقلة، وسيستخدم كل مؤشر ترابط مجموعة جديدة من معرفات async.
نظرة عامة
فيما يلي نظرة عامة بسيطة على واجهة برمجة التطبيقات العامة.
import async_hooks from 'node:async_hooks'
// إرجاع معرف سياق التنفيذ الحالي.
const eid = async_hooks.executionAsyncId()
// إرجاع معرف المقبض المسؤول عن تشغيل callback نطاق التنفيذ الحالي للاتصال.
const tid = async_hooks.triggerAsyncId()
// إنشاء مثيل AsyncHook جديد. كل هذه الـ callbacks اختيارية.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// السماح لـ callbacks لهذا المثال AsyncHook بالاتصال. هذا ليس إجراءً ضمنيًا بعد تشغيل المُنشئ، ويجب تشغيله صراحةً لبدء تشغيل الـ callbacks.
asyncHook.enable()
// تعطيل الاستماع إلى أحداث غير متزامنة جديدة.
asyncHook.disable()
//
// فيما يلي الـ callbacks التي يمكن تمريرها إلى createHook().
//
// يتم استدعاء init() أثناء إنشاء الكائن. قد لا يكون قد اكتمل إنشاء المورد عند تشغيل هذا الـ callback. لذلك، قد لا يتم ملء جميع حقول المورد المشار إليها بواسطة "asyncId".
function init(asyncId, type, triggerAsyncId, resource) {}
// يتم استدعاء before() قبل مباشرة استدعاء callback المورد. يمكن استدعاءه 0-N مرات للمقابض (مثل TCPWrap)، وسيتم استدعائه مرة واحدة فقط للطلبات (مثل FSReqCallback).
function before(asyncId) {}
// يتم استدعاء after() بعد مباشرة انتهاء callback المورد.
function after(asyncId) {}
// يتم استدعاء destroy() عند تدمير المورد.
function destroy(asyncId) {}
// يتم استدعاء promiseResolve() فقط لموارد الوعود، عندما يتم استدعاء دالة resolve() الممررة إلى مُنشئ Promise
// (إما مباشرة أو من خلال وسائل أخرى لحل وعد).
function promiseResolve(asyncId) {}
const async_hooks = require('node:async_hooks')
// إرجاع معرف سياق التنفيذ الحالي.
const eid = async_hooks.executionAsyncId()
// إرجاع معرف المقبض المسؤول عن تشغيل callback نطاق التنفيذ الحالي للاتصال.
const tid = async_hooks.triggerAsyncId()
// إنشاء مثيل AsyncHook جديد. كل هذه الـ callbacks اختيارية.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })
// السماح لـ callbacks لهذا المثال AsyncHook بالاتصال. هذا ليس إجراءً ضمنيًا بعد تشغيل المُنشئ، ويجب تشغيله صراحةً لبدء تشغيل الـ callbacks.
asyncHook.enable()
// تعطيل الاستماع إلى أحداث غير متزامنة جديدة.
asyncHook.disable()
//
// فيما يلي الـ callbacks التي يمكن تمريرها إلى createHook().
//
// يتم استدعاء init() أثناء إنشاء الكائن. قد لا يكون قد اكتمل إنشاء المورد عند تشغيل هذا الـ callback. لذلك، قد لا يتم ملء جميع حقول المورد المشار إليها بواسطة "asyncId".
function init(asyncId, type, triggerAsyncId, resource) {}
// يتم استدعاء before() قبل مباشرة استدعاء callback المورد. يمكن استدعاءه 0-N مرات للمقابض (مثل TCPWrap)، وسيتم استدعائه مرة واحدة فقط للطلبات (مثل FSReqCallback).
function before(asyncId) {}
// يتم استدعاء after() بعد مباشرة انتهاء callback المورد.
function after(asyncId) {}
// يتم استدعاء destroy() عند تدمير المورد.
function destroy(asyncId) {}
// يتم استدعاء promiseResolve() فقط لموارد الوعود، عندما يتم استدعاء دالة resolve() الممررة إلى مُنشئ Promise
// (إما مباشرة أو من خلال وسائل أخرى لحل وعد).
function promiseResolve(asyncId) {}
async_hooks.createHook(callbacks)
مضاف في: v8.1.0
callbacks
<Object> استدعاءات خطاف للتسجيلinit
<Function> استدعاءinit
.before
<Function> استدعاءbefore
.after
<Function> استدعاءafter
.destroy
<Function> استدعاءdestroy
.promiseResolve
<Function> استدعاءpromiseResolve
.
المرتجعات: <AsyncHook> مثيل يستخدم لتعطيل وتمكين الخطافات
يسجل الدوال التي سيتم استدعاؤها لأحداث عمر مختلفة لكل عملية غير متزامنة.
يتم استدعاء الاستدعاءات init()
/before()
/after()
/destroy()
لحدث غير متزامن خلال عمر المورد.
جميع الاستدعاءات اختيارية. على سبيل المثال، إذا كانت هناك حاجة فقط لتتبع تنظيف الموارد، فيجب تمرير استدعاء destroy
فقط. توجد تفاصيل جميع الدوال التي يمكن تمريرها إلى callbacks
في قسم استدعاءات الخطاف.
import { createHook } from 'node:async_hooks'
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
const async_hooks = require('node:async_hooks')
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
سيتم توريث الاستدعاءات عبر سلسلة النموذج الأولي:
class MyAsyncCallbacks {
init(asyncId, type, triggerAsyncId, resource) {}
destroy(asyncId) {}
}
class MyAddedCallbacks extends MyAsyncCallbacks {
before(asyncId) {}
after(asyncId) {}
}
const asyncHook = async_hooks.createHook(new MyAddedCallbacks())
بما أن الوعود موارد غير متزامنة يتم تتبع دورة حياتها عبر آلية خطافات غير متزامنة، يجب ألا تكون استدعاءات init()
, before()
, after()
, وdestroy()
دوالًا غير متزامنة تُرجع وعودًا.
معالجة الأخطاء
إذا ألقى أي من عمليات استدعاء AsyncHook
استثناءً، فسيقوم التطبيق بطباعة تتبع المكدس والخروج. لا يتبع مسار الخروج مسار الاستثناء غير المُعالَج، ولكن يتم إزالة جميع مُستمعي 'uncaughtException'
، مما يُجبر العملية على الخروج. ستظل عمليات استدعاء 'exit'
مُستدعاة ما لم يتم تشغيل التطبيق باستخدام --abort-on-uncaught-exception
، وفي هذه الحالة سيتم طباعة تتبع المكدس ويخرج التطبيق، تاركًا ملفًا أساسيًا.
سبب هذا السلوك في معالجة الأخطاء هو أن هذه الاستدعاءات تعمل في نقاط مُتقلّبة محتملًا في عمر الكائن، على سبيل المثال أثناء إنشاء الفئة وتدميرها. وبسبب هذا، يُعتبر من الضروري إيقاف العملية بسرعة لمنع الإيقاف غير المقصود في المستقبل. هذا الأمر قابل للتغيير في المستقبل إذا تم إجراء تحليل شامل لضمان أن يتمكن الاستثناء من اتباع تدفق التحكم العادي دون آثار جانبية غير مقصودة.
الطباعة في عمليات استدعاء AsyncHook
بسبب أن الطباعة على وحدة التحكم هي عملية غير متزامنة، فإن console.log()
سيؤدي إلى استدعاء عمليات استدعاء AsyncHook
. سيؤدي استخدام console.log()
أو عمليات غير متزامنة مماثلة داخل دالة استدعاء AsyncHook
إلى تكرار لانهائي. حل سهل لهذا عند التصحيح هو استخدام عملية تسجيل متزامنة مثل fs.writeFileSync(file, msg, flag)
. سيقوم هذا بالطباعة على الملف ولن يستدعي AsyncHook
بشكل متكرر لأنه متزامن.
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'
function debug(...args) {
// استخدم دالة مثل هذه عند التصحيح داخل استدعاء AsyncHook
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
const fs = require('node:fs')
const util = require('node:util')
function debug(...args) {
// استخدم دالة مثل هذه عند التصحيح داخل استدعاء AsyncHook
fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}
إذا كانت هناك حاجة إلى عملية غير متزامنة للتسجيل، فمن الممكن تتبع سبب العملية غير المتزامنة باستخدام المعلومات التي يوفرها AsyncHook
نفسه. يجب بعد ذلك تخطي التسجيل عندما كان التسجيل نفسه هو الذي تسبب في استدعاء استدعاء AsyncHook
. من خلال القيام بذلك، يتم كسر التكرار اللانهائي.
الصنف: AsyncHook
يعرض الصنف AsyncHook
واجهة لتتبع أحداث دورة حياة العمليات غير المتزامنة.
asyncHook.enable()
- المُرجَع: <AsyncHook> مرجع إلى
asyncHook
.
يقوم بتمكين المُدعيات لمثيل AsyncHook
المُعطى. إذا لم يتم توفير أي مُدعيات، فإن التمكين يكون عملية بدون تأثير.
يتم تعطيل مثيل AsyncHook
افتراضيًا. إذا كان يجب تمكين مثيل AsyncHook
فورًا بعد إنشائه، فيمكن استخدام النمط التالي.
import { createHook } from 'node:async_hooks'
const hook = createHook(callbacks).enable()
const async_hooks = require('node:async_hooks')
const hook = async_hooks.createHook(callbacks).enable()
asyncHook.disable()
- المُرجَع: <AsyncHook> مرجع إلى
asyncHook
.
يقوم بتعطيل المُدعيات لمثيل AsyncHook
المُعطى من مجموعة AsyncHook
العالمية للمدعيات التي سيتم تنفيذها. بمجرد تعطيل المُدعى، لن يتم استدعاءه مرة أخرى حتى يتم تمكينه.
من أجل اتساق واجهة البرمجة، تقوم disable()
أيضًا بإرجاع مثيل AsyncHook
.
مُدعيات المُدعى
تم تصنيف الأحداث الرئيسية في دورة حياة الأحداث غير المتزامنة إلى أربعة مجالات: إنشاء مثيل، قبل/بعد استدعاء المُدعى، وعندما يتم تدمير المثيل.
init(asyncId, type, triggerAsyncId, resource)
asyncId
<number> معرف فريد لمورد غير متزامن.type
<string> نوع المورد غير المتزامن.triggerAsyncId
<number> المعرف الفريد للمورد غير المتزامن الذي تم فيه إنشاء هذا المورد غير المتزامن.resource
<Object> مرجع إلى المورد الذي يمثل العملية غير المتزامنة، يجب إطلاقه أثناء destroy.
يتم استدعاء هذه الدالة عند إنشاء صنف لديه إمكانية لإصدار حدث غير متزامن. هذا لا يعني أن المثيل يجب أن يستدعي before
/after
قبل استدعاء destroy
، بل أن الإمكانية موجودة فقط.
يمكن ملاحظة هذا السلوك من خلال القيام بشيء مثل فتح مورد ثم إغلاقه قبل استخدام المورد. يوضح المقطع التالي هذا.
import { createServer } from 'node:net'
createServer().listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
require('node:net')
.createServer()
.listen(function () {
this.close()
})
// OR
clearTimeout(setTimeout(() => {}, 10))
يتم تعيين معرف فريد لكل مورد جديد ضمن نطاق مثيل Node.js الحالي.
type
type
هو سلسلة تُحدد نوع المورد الذي تسبب في استدعاء init
. بشكل عام، سيتوافق مع اسم مُنشئ المورد.
يمكن أن يتغير نوع الموارد التي أنشأتها Node.js نفسها في أي إصدار من Node.js. تتضمن القيم الصالحة TLSWRAP
, TCPWRAP
, TCPSERVERWRAP
, GETADDRINFOREQWRAP
, FSREQCALLBACK
, Microtask
, و Timeout
. راجع رمز مصدر إصدار Node.js المستخدم للحصول على القائمة الكاملة.
علاوة على ذلك، يقوم مستخدمو AsyncResource
بإنشاء موارد غير متزامنة مستقلة عن Node.js نفسها.
يوجد أيضًا نوع مورد PROMISE
، والذي يُستخدم لتتبع مثيلات Promise
والعمل غير المتزامن المُجدول بواسطة هذه المثيلات.
يُمكن للمستخدمين تعريف type
الخاص بهم عند استخدام واجهة برمجة تطبيقات المُدمج العام.
من الممكن حدوث تصادمات في أسماء الأنواع. يُشجّع المُدمجون على استخدام بادئات فريدة، مثل اسم حزمة npm، لمنع التصادمات عند الاستماع إلى الخطافات.
triggerAsyncId
triggerAsyncId
هو asyncId
للمورد الذي تسبب (أو "أثار") في تهيئة المورد الجديد والذي تسبب في استدعاء init
. هذا يختلف عن async_hooks.executionAsyncId()
الذي يُظهر فقط متى تم إنشاء المورد، بينما يُظهر triggerAsyncId
لماذا تم إنشاء المورد.
فيما يلي عرض توضيحي بسيط لـ triggerAsyncId
:
import { createHook, executionAsyncId } from 'node:async_hooks'
import { stdout } from 'node:process'
import net from 'node:net'
import fs from 'node:fs'
createHook({
init(asyncId, type, triggerAsyncId) {
const eid = executionAsyncId()
fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
}).enable()
net.createServer(conn => {}).listen(8080)
const { createHook, executionAsyncId } = require('node:async_hooks')
const { stdout } = require('node:process')
const net = require('node:net')
const fs = require('node:fs')
createHook({
init(asyncId, type, triggerAsyncId) {
const eid = executionAsyncId()
fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
}).enable()
net.createServer(conn => {}).listen(8080)
الإخراج عند الوصول إلى الخادم باستخدام nc localhost 8080
:
TCPSERVERWRAP(5): trigger: 1 execution: 1
TCPWRAP(7): trigger: 5 execution: 0
TCPSERVERWRAP
هو الخادم الذي يستقبل الاتصالات.
TCPWRAP
هو الاتصال الجديد من العميل. عند إجراء اتصال جديد، يتم إنشاء مثيل TCPWrap
على الفور. يحدث هذا خارج أي مُكدس جافا سكريبت. (تعني قيمة executionAsyncId()
وهي 0
أنه يتم تنفيذه من C++ بدون مُكدس جافا سكريبت أعلاه). مع هذه المعلومات فقط، سيكون من المستحيل ربط الموارد معًا من حيث ما تسبب في إنشائها، لذلك تُعطى مهمة triggerAsyncId
نشر أي مورد مسؤول عن وجود المورد الجديد.
resource
resource
هو كائن يمثل المورد غير المتزامن الفعلي الذي تم تهيئته. يمكن تحديد واجهة برمجة التطبيقات للوصول إلى الكائن بواسطة منشئ المورد. الموارد التي أنشأتها Node.js نفسها هي موارد داخلية وقد تتغير في أي وقت. لذلك ، لم يتم تحديد أي واجهة برمجة تطبيقات لهذه الموارد.
في بعض الحالات ، يتم إعادة استخدام كائن المورد لأسباب تتعلق بالأداء ، وبالتالي ليس من الآمن استخدامه كمفتاح في WeakMap
أو إضافة خصائص إليه.
مثال سياق غير متزامن
يتم تغطية حالة استخدام تتبع السياق بواسطة واجهة برمجة التطبيقات الثابتة AsyncLocalStorage
. لا يوضح هذا المثال سوى عملية الخطافات غير المتزامنة ، ولكن AsyncLocalStorage
تناسب بشكل أفضل حالة الاستخدام هذه.
فيما يلي مثال يحتوي على معلومات إضافية حول المكالمات إلى init
بين مكالمات before
و after
، وتحديدًا كيف ستبدو دالة الاستدعاء العودية لـ listen ()
. التنسيق الناتج أكثر تفصيلاً قليلاً لتسهيل رؤية سياق الاستدعاء.
import async_hooks from 'node:async_hooks'
import fs from 'node:fs'
import net from 'node:net'
import { stdout } from 'node:process'
const { fd } = stdout
let indent = 0
async_hooks
.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId()
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
before(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}before: ${asyncId}\n`)
indent += 2
},
after(asyncId) {
indent -= 2
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}after: ${asyncId}\n`)
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}destroy: ${asyncId}\n`)
},
})
.enable()
net
.createServer(() => {})
.listen(8080, () => {
// دعونا ننتظر 10 مللي ثانية قبل تسجيل بدء الخادم.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId())
}, 10)
})
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
const net = require('node:net')
const { fd } = process.stdout
let indent = 0
async_hooks
.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId()
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
},
before(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}before: ${asyncId}\n`)
indent += 2
},
after(asyncId) {
indent -= 2
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}after: ${asyncId}\n`)
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent)
fs.writeSync(fd, `${indentStr}destroy: ${asyncId}\n`)
},
})
.enable()
net
.createServer(() => {})
.listen(8080, () => {
// دعونا ننتظر 10 مللي ثانية قبل تسجيل بدء الخادم.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId())
}, 10)
})
الإخراج من بدء تشغيل الخادم فقط:
TCPSERVERWRAP(5): trigger: 1 execution: 1
TickObject(6): trigger: 5 execution: 1
before: 6
Timeout(7): trigger: 6 execution: 6
after: 6
destroy: 6
before: 7
>>> 7
TickObject(8): trigger: 7 execution: 7
after: 7
before: 8
after: 8
كما هو موضح في المثال ، يحدد كل من executionAsyncId()
و execution
قيمة سياق التنفيذ الحالي ؛ الذي يحدده استدعاءات إلى before
و after
.
استخدام execution
فقط لإنشاء رسم بياني لتخصيص الموارد ينتج عنه ما يلي:
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
TCPSERVERWRAP
ليس جزءًا من هذا الرسم البياني ، على الرغم من أنه كان سبب استدعاء console.log ()
. هذا لأنه ربط بمنفذ بدون اسم مضيف هو عملية متزامنة ، ولكن للحفاظ على واجهة برمجة تطبيقات غير متزامنة تمامًا ، يتم وضع دالة الاستدعاء العودية للمستخدم في process.nextTick ()
. وهذا هو السبب في وجود TickObject
في الإخراج وهو "والد" لدالة الاستدعاء العودية لـ .listen ()
.
لا يوضح الرسم البياني إلا متى تم إنشاء المورد ، وليس لماذا ، لذلك لتتبع لماذا استخدم triggerAsyncId
. والتي يمكن تمثيلها بالرسم البياني التالي:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
عندما يتم بدء تشغيل عملية غير متزامنة (مثل استقبال خادم TCP لاتصال جديد) أو اكتمالها (مثل كتابة البيانات على القرص)، يتم استدعاء دالة الاستدعاء لإعلام المستخدم. يتم استدعاء دالة الاستدعاء before
قبل تنفيذ دالة الاستدعاء المذكورة مباشرةً. asyncId
هو معرف فريد مُعين للمورد على وشك تنفيذ دالة الاستدعاء.
سيتم استدعاء دالة الاستدعاء before
من 0 إلى N مرة. عادةً ما يتم استدعاء دالة الاستدعاء before
0 مرة إذا تم إلغاء العملية غير المتزامنة، أو على سبيل المثال، إذا لم يستقبل خادم TCP أي اتصالات. ستقوم الموارد غير المتزامنة الدائمة مثل خادم TCP عادةً باستدعاء دالة الاستدعاء before
عدة مرات، بينما ستقوم عمليات أخرى مثل fs.open()
باستدعائها مرة واحدة فقط.
after(asyncId)
asyncId
<number>
يتم استدعاء هذه الدالة مباشرة بعد اكتمال دالة الاستدعاء المحددة في before
.
إذا حدث استثناء غير معالج أثناء تنفيذ دالة الاستدعاء، فسيتم تشغيل after
بعد إرسال حدث 'uncaughtException'
أو تشغيل مُعالِج domain
.
destroy(asyncId)
asyncId
<number>
يتم استدعاء هذه الدالة بعد تدمير المورد المقابل لـ asyncId
. كما يتم استدعاؤها بشكل غير متزامن من واجهة برمجة التطبيقات المُضمنة emitDestroy()
.
تعتمد بعض الموارد على جمع القمامة للتنظيف، لذلك إذا تم إنشاء مرجع لكائن resource
المُمرر إلى init
، فمن الممكن ألا يتم استدعاء destroy
أبدًا، مما يتسبب في حدوث تسرب للذاكرة في التطبيق. إذا لم يعتمد المورد على جمع القمامة، فلن تكون هذه مشكلة.
يؤدي استخدام مُعلّق destroy
إلى زيادة في الأعباء الإضافية لأنه يُمكّن تتبع مثيلات Promise
عبر جامِع القمامة.
promiseResolve(asyncId)
مُضاف في: v8.6.0
asyncId
<number>
يتم استدعاء هذه الدالة عندما يتم استدعاء دالة resolve
المُمررة إلى مُنشئ Promise
(إما مباشرة أو من خلال وسائل أخرى لحل وعد).
لا تقوم resolve()
بأي عمل متزامن قابل للملاحظة.
لا يكون Promise
بالضرورة مُنجزًا أو مرفوضًا في هذه المرحلة إذا تم حل Promise
من خلال افتراض حالة Promise
آخر.
new Promise(resolve => resolve(true)).then(a => {})
تستدعي دوال الاستدعاء التالية:
init for PROMISE with id 5, trigger id: 1
promise resolve 5 # corresponds to resolve(true)
init for PROMISE with id 6, trigger id: 5 # the Promise returned by then()
before 6 # the then() callback is entered
promise resolve 6 # the then() callback resolves the promise by returning
after 6
async_hooks.executionAsyncResource()
تم الإضافة في: v13.9.0، v12.17.0
- قيمة الإرجاع: <Object> المورد الذي يمثل التنفيذ الحالي. مفيد لتخزين البيانات داخل المورد.
غالباً ما تكون كائنات الموارد التي تُرجعها executionAsyncResource()
كائنات معالجة داخلية في Node.js ذات واجهات برمجة تطبيقات غير موثقة. إن استخدام أي دالات أو خصائص على الكائن من المرجح أن يؤدي إلى تعطل تطبيقك ويجب تجنب ذلك.
سيؤدي استخدام executionAsyncResource()
في سياق التنفيذ الرئيسي إلى إرجاع كائن فارغ لأنه لا يوجد كائن معالجة أو طلب لاستخدامه، لكن وجود كائن يمثل المستوى الرئيسي قد يكون مفيدًا.
import { open } from 'node:fs'
import { executionAsyncId, executionAsyncResource } from 'node:async_hooks'
console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(new URL(import.meta.url), 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})
const { open } = require('node:fs')
const { executionAsyncId, executionAsyncResource } = require('node:async_hooks')
console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(__filename, 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})
يمكن استخدام هذا لتنفيذ تخزين محلي للاستمرار دون استخدام Map
للتتبع لتخزين البيانات الوصفية:
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // رمز خاص لتجنب التلوث
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource()
if (cr) {
resource[sym] = cr[sym]
}
},
}).enable()
const server = createServer((req, res) => {
executionAsyncResource()[sym] = { state: req.url }
setTimeout(function () {
res.end(JSON.stringify(executionAsyncResource()[sym]))
}, 100)
}).listen(3000)
const { createServer } = require('node:http')
const { executionAsyncId, executionAsyncResource, createHook } = require('node:async_hooks')
const sym = Symbol('state') // رمز خاص لتجنب التلوث
createHook({
init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource()
if (cr) {
resource[sym] = cr[sym]
}
},
}).enable()
const server = createServer((req, res) => {
executionAsyncResource()[sym] = { state: req.url }
setTimeout(function () {
res.end(JSON.stringify(executionAsyncResource()[sym]))
}, 100)
}).listen(3000)
async_hooks.executionAsyncId()
[السجل]
الإصدار | التغييرات |
---|---|
v8.2.0 | أعيدت تسميته من currentId . |
v8.1.0 | تمت الإضافة في: v8.1.0 |
- القيمة المُرجعة: <number>
asyncId
لسياق التنفيذ الحالي. مفيد لتتبع متى يتم إجراء مكالمة.
import { executionAsyncId } from 'node:async_hooks'
import fs from 'node:fs'
console.log(executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
console.log(executionAsyncId()) // 6 - open()
})
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
console.log(async_hooks.executionAsyncId()) // 1 - bootstrap
const path = '.'
fs.open(path, 'r', (err, fd) => {
console.log(async_hooks.executionAsyncId()) // 6 - open()
})
يُرتبط المعرف المُرجّع من executionAsyncId()
بتوقيت التنفيذ، وليس بالسببية (التي يغطيها triggerAsyncId()
):
const server = net
.createServer(conn => {
// يُرجّع معرف الخادم، وليس للاتصال الجديد، لأنّ دالة المُراجعة تُنفَّذ ضمن نطاق تنفيذ MakeCallback() الخاص بالخادم.
async_hooks.executionAsyncId()
})
.listen(port, () => {
// يُرجّع معرف كائن TickObject (process.nextTick()) لأنّ جميع دوال المُراجعة المُمرَّرة إلى .listen() مُغلَّفة في nextTick().
async_hooks.executionAsyncId()
})
قد لا تحصل سياقات الوعود على executionAsyncIds
دقيقة بشكل افتراضي. راجع القسم الخاص بـ تتبع تنفيذ الوعود.
async_hooks.triggerAsyncId()
- القيمة المُرجعة: <number> معرف المورد المسؤول عن استدعاء دالة المُراجعة التي يتم تنفيذها حاليًا.
const server = net
.createServer(conn => {
// المورد الذي تسبب (أو أثار) في استدعاء دالة المُراجعة هذه كان ذاك الخاص بالاتصال الجديد. وبالتالي، فإنّ القيمة المُرجَّعة لـ triggerAsyncId() هي asyncId لـ "conn".
async_hooks.triggerAsyncId()
})
.listen(port, () => {
// على الرغم من أنّ جميع دوال المُراجعة المُمرَّرة إلى .listen() مُغلَّفة في nextTick()، إلّا أنّ دالة المُراجعة نفسها موجودة لأنّ المكالمة إلى .listen() الخاصة بالخادم قد تمّت. لذا، ستكون القيمة المُرجَّعة هي معرف الخادم.
async_hooks.triggerAsyncId()
})
قد لا تحصل سياقات الوعود على triggerAsyncId
s صالحة بشكل افتراضي. راجع القسم الخاص بـ تتبع تنفيذ الوعود.
async_hooks.asyncWrapProviders
مضاف في: v17.2.0، v16.14.0
- الرجوع: خريطة لأنواع الموفر إلى معرف رقمي مقابِل. تحتوي هذه الخريطة على جميع أنواع الأحداث التي قد تُصدرها حدث
async_hooks.init()
.
هذه الميزة تُلغي الاستخدام المُهمَل لـ process.binding('async_wrap').Providers
. انظر: DEP0111
تتبع تنفيذ الوعود
بشكل افتراضي، لا يتم تعيين asyncId
s لعمليات تنفيذ الوعود نظرًا لطبيعة واجهة برمجة تطبيقات استقراء الوعود الباهظة نسبيًا المُقدمة من قبل V8. هذا يعني أن البرامج التي تستخدم الوعود أو async
/await
لن تحصل على معرفات التنفيذ والتشغيل الصحيحة لسياقات استدعاء الوعود بشكل افتراضي.
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// ينتج:
// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks')
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// ينتج:
// eid 1 tid 0
لاحظ أن استدعاء الدالة then()
يدعي أنه تم تنفيذه في سياق النطاق الخارجي على الرغم من وجود قفزة غير متزامنة. أيضًا، قيمة triggerAsyncId
هي 0
، مما يعني أننا نفتقر إلى السياق حول المورد الذي تسبب (أثار) في تنفيذ استدعاء الدالة then()
.
يُمكن تثبيت خطافات غير متزامنة عبر async_hooks.createHook
لتمكين تتبع تنفيذ الوعود:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // يُجبر على تمكين PromiseHooks.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// ينتج:
// eid 7 tid 6
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')
createHook({ init() {} }).enable() // يُجبر على تمكين PromiseHooks.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// ينتج:
// eid 7 tid 6
في هذا المثال، أضافت أي دالة خطاف فعلية تتبع الوعود. يوجد وعدان في المثال أعلاه؛ الوعد الذي تم إنشاؤه بواسطة Promise.resolve()
والوعد الذي تم إرجاعه بواسطة استدعاء الدالة then()
. في المثال أعلاه، حصل الوعد الأول على asyncId
6
وحصل الأخير على asyncId
7
. أثناء تنفيذ استدعاء الدالة then()
، نقوم بالتنفيذ في سياق الوعد ذي asyncId
7
. تم تشغيل هذا الوعد بواسطة المورد غير المتزامن 6
.
دقة أخرى تتعلق بالوعود هي أن استدعاءات الدوال before
و after
يتم تشغيلها فقط على الوعود المتسلسلة. هذا يعني أن الوعود التي لم يتم إنشاؤها بواسطة then()
/catch()
لن يتم تشغيل استدعاءات الدوال before
و after
عليها. لمزيد من التفاصيل، راجع تفاصيل واجهة برمجة تطبيقات V8 PromiseHooks.
واجهة برمجة تطبيقات مضمِّن JavaScript
يمكن لمطوّري المكتبات الذين يتعاملون مع مواردهم غير المتزامنة الخاصة التي تؤدي مهام مثل إدخال/إخراج، أو تجميع الاتصالات، أو إدارة قوائم انتظار الاستدعاءات العكسية، استخدام واجهة برمجة تطبيقات JavaScript AsyncResource
بحيث يتم استدعاء جميع الاستدعاءات العكسية المناسبة.
الفئة: AsyncResource
تم نقل وثائق هذه الفئة إلى AsyncResource
.
الفئة: AsyncLocalStorage
تم نقل وثائق هذه الفئة إلى AsyncLocalStorage
.