Skip to content

المجال

[السجل]

الإصدارالتغييرات
v8.8.0لم تعد أي وعود تم إنشاؤها في سياقات VM تمتلك خاصية .domain. ومع ذلك، لا تزال مُعالجاتها تُنفذ في المجال المناسب، ولا تزال الوعود التي تم إنشاؤها في السياق الرئيسي تمتلك خاصية .domain.
v8.0.0يتم الآن استدعاء مُعالجات الوعود في المجال الذي تم فيه إنشاء أول وعد في السلسلة.
1.4.2مُحذّر منذ: v1.4.2

[مستقر: 0 - مُحذّر]

مستقر: 0 الثبات: 0 - مُحذّر

المصدر: lib/domain.js

هذا المودول قيد الإلغاء. بمجرد الانتهاء من واجهة برمجة التطبيقات البديلة، سيتم إلغاء هذا المودول بالكامل. يجب على معظم المطورين عدم استخدام هذا المودول. قد يعتمد المستخدمون الذين يجب عليهم بالضرورة الحصول على الوظائف التي توفرها المجالات عليه مؤقتًا، ولكن يجب أن يتوقعوا الترحيل إلى حل مختلف في المستقبل.

توفر المجالات طريقة لمعالجة العديد من عمليات الإدخال/الإخراج المختلفة كمجموعة واحدة. إذا قام أي من مُرسلي الأحداث أو الاستدعاءات العائدة المسجلة في مجال ما بإرسال حدث 'error'، أو قام بإلقاء خطأ، فسيتم إخطار كائن المجال، بدلاً من فقد سياق الخطأ في مُعالج process.on('uncaughtException')، أو التسبب في خروج البرنامج فوراً برمز خطأ.

تحذير: لا تتجاهل الأخطاء!

لا تُعد مُعالجات أخطاء المجال بديلاً لإغلاق عملية عند حدوث خطأ.

بسبب طبيعة كيفية عمل throw في جافا سكريبت، لا توجد تقريبًا أي طريقة آمنة "للاستئناف من حيث توقف"، دون تسريب المراجع، أو إنشاء نوع آخر من الحالة الهشة غير المحددة.

الطريقة الأكثر أمانًا للاستجابة لخطأ مُلقى هي إيقاف العملية. بالطبع، في خادم ويب عادي، قد يكون هناك العديد من الاتصالات المفتوحة، وليس من المعقول إيقافها فجأة بسبب خطأ تم إثارة بواسطة شخص آخر.

النهج الأفضل هو إرسال استجابة خطأ إلى الطلب الذي أثار الخطأ، مع السماح للآخرين بالانتهاء في وقتهم الطبيعي، والتوقف عن الاستماع إلى طلبات جديدة في ذلك العامل.

بهذه الطريقة، يرتبط استخدام domain ارتباطًا وثيقًا بوحدة cluster، حيث يمكن للعملية الأساسية إنشاء عامل جديد عندما يواجه عامل خطأ. بالنسبة لبرامج Node.js التي تتوسع إلى أجهزة متعددة، يمكن أن يلاحظ وكيل الإنهاء أو سجل الخدمة الفشل، ويتفاعل وفقًا لذلك.

على سبيل المثال، هذه ليست فكرة جيدة:

js
// XXX تحذير! فكرة سيئة!

const d = require('node:domain').create()
d.on('error', er => {
  // لن يتسبب الخطأ في تعطل العملية، لكن ما يفعله أسوأ!
  // على الرغم من أننا منعنا إعادة تشغيل العملية المفاجئة، إلا أننا نُسرب
  // الكثير من الموارد إذا حدث هذا مطلقًا.
  // هذا ليس أفضل من process.on('uncaughtException')!
  console.log(`خطأ، ولكن حسناً ${er.message}`)
})
d.run(() => {
  require('node:http')
    .createServer((req, res) => {
      handleRequest(req, res)
    })
    .listen(PORT)
})

باستخدام سياق مجال، وقدرة المرونة في فصل برنامجنا إلى عمليات عامل متعددة، يمكننا التفاعل بشكل أكثر ملاءمة، ومعالجة الأخطاء بأمان أكبر بكثير.

js
// أفضل بكثير!

const cluster = require('node:cluster')
const PORT = +process.env.PORT || 1337

if (cluster.isPrimary) {
  // سيناريو أكثر واقعية سيكون به أكثر من عاملين،
  // وربما لا يضع الرئيسي والعامل في نفس الملف.
  //
  // من الممكن أيضًا أن تصبح أكثر أناقة قليلاً بشأن التسجيل، و
  // تنفيذ أي منطق مخصص مطلوب لمنع هجمات الحرمان من الخدمة
  // وغيرها من السلوكيات السيئة.
  //
  // انظر الخيارات في وثائق cluster.
  //
  // الشيء المهم هو أن الرئيسي لا يفعل الكثير،
  // مما يزيد من قدرة المرونة لدينا تجاه الأخطاء غير المتوقعة.

  cluster.fork()
  cluster.fork()

  cluster.on('disconnect', worker => {
    console.error('انفصال!')
    cluster.fork()
  })
} else {
  // العامل
  //
  // هذا هو المكان الذي نضع فيه أخطائنا!

  const domain = require('node:domain')

  // انظر وثائق cluster لمزيد من التفاصيل حول استخدام
  // عمليات العامل لخدمة الطلبات. كيف تعمل، التحذيرات، إلخ.

  const server = require('node:http').createServer((req, res) => {
    const d = domain.create()
    d.on('error', er => {
      console.error(`خطأ ${er.stack}`)

      // نحن في منطقة خطيرة!
      // حسب التعريف، حدث شيء غير متوقع،
      // وهو ما لم نكن نريده على الأرجح.
      // أي شيء يمكن أن يحدث الآن! كن حذرًا جدًا!

      try {
        // تأكد من إغلاقنا في غضون 30 ثانية
        const killtimer = setTimeout(() => {
          process.exit(1)
        }, 30000)
        // ولكن لا تبقِ العملية مفتوحة فقط لذلك!
        killtimer.unref()

        // توقف عن قبول طلبات جديدة.
        server.close()

        // أبلغ الرئيسي بأننا متوفون. سيؤدي هذا إلى إطلاق
        // 'disconnect' في الرئيسي cluster، ثم سيقوم بإنشاء
        // عامل جديد.
        cluster.worker.disconnect()

        // حاول إرسال خطأ إلى الطلب الذي أثار المشكلة
        res.statusCode = 500
        res.setHeader('content-type', 'text/plain')
        res.end('عفواً، حدثت مشكلة!\n')
      } catch (er2) {
        // حسناً، ليس هناك الكثير الذي يمكننا فعله في هذه المرحلة.
        console.error(`خطأ في إرسال 500! ${er2.stack}`)
      }
    })

    // نظرًا لإنشاء req و res قبل وجود هذا المجال،
    // نحتاج إلى إضافتها صراحةً.
    // انظر شرح الربط الضمني مقابل الصريح أدناه.
    d.add(req)
    d.add(res)

    // قم الآن بتشغيل دالة المُعالج في المجال.
    d.run(() => {
      handleRequest(req, res)
    })
  })
  server.listen(PORT)
}

// هذا الجزء ليس مهمًا. مجرد شيء توجيه مثال.
// ضع منطق تطبيق رائع هنا.
function handleRequest(req, res) {
  switch (req.url) {
    case '/error':
      // نقوم ببعض الأشياء غير المتزامنة، ثم...
      setTimeout(() => {
        // يا خسارة!
        flerb.bark()
      }, timeout)
      break
    default:
      res.end('ok')
  }
}

إضافات إلى كائنات Error

في كل مرة يتم توجيه كائن Error عبر مجال، يتم إضافة بعض الحقول الإضافية إليه.

  • error.domain المجال الذي تعامل مع الخطأ أولاً.
  • error.domainEmitter مُصدر الحدث الذي أصدر حدث 'error' مع كائن الخطأ.
  • error.domainBound دالة الاستدعاء المرتبطة بالمجال، والتي تم تمرير خطأ إليها كحجة أولى.
  • error.domainThrown قيمة منطقية تشير إلى ما إذا كان الخطأ قد تم طرحه، أو إصداره، أو تم تمريره إلى دالة استدعاء مرتبطة.

الربط الضمني

إذا كانت المجالات قيد الاستخدام، فسيتم ربط جميع كائنات EventEmitter الجديدة (بما في ذلك كائنات Stream، والطلبات، والاستجابات، وما إلى ذلك) بشكل ضمني بالمجال النشط في وقت إنشائها.

بالإضافة إلى ذلك، سيتم ربط عمليات الاستدعاء المُمررة إلى طلبات حلقة الحدث منخفضة المستوى (مثل fs.open()، أو طرق أخرى تأخذ عمليات الاستدعاء) تلقائيًا بالمجال النشط. إذا طرحت هذه الطلبات خطأ، فسيلتقط المجال الخطأ.

ولمنع الاستخدام المفرط للذاكرة، لا يتم إضافة كائنات Domain نفسها ضمنيًا كأبناء للمجال النشط. إذا كان الأمر كذلك، فسيكون من السهل جدًا منع جمع كائنات الطلب والاستجابة بشكل صحيح.

لتعشيش كائنات Domain كأبناء لـ Domain أب، يجب إضافتها صراحةً.

يوجه الربط الضمني الأخطاء المطروحة وأحداث 'error' إلى حدث 'error' الخاص بـ Domain، لكنه لا يسجل EventEmitter على Domain. الربط الضمني يهتم فقط بالأخطاء المطروحة وأحداث 'error'.

الربط الصريح

في بعض الأحيان، لا يكون المجال المستخدم هو المجال الذي يجب استخدامه لمصدر حدث معين. أو، قد يكون تم إنشاء مصدر الحدث في سياق مجال واحد، ولكن يجب ربطه بمجال آخر.

على سبيل المثال، قد يكون هناك مجال واحد قيد الاستخدام لخادم HTTP، ولكن ربما نرغب في استخدام مجال منفصل لكل طلب.

هذا ممكن من خلال الربط الصريح.

js
// إنشاء مجال أعلى مستوى للخادم
const domain = require('node:domain')
const http = require('node:http')
const serverDomain = domain.create()

serverDomain.run(() => {
  // تم إنشاء الخادم ضمن نطاق serverDomain
  http
    .createServer((req, res) => {
      // تم إنشاء Req و Res أيضًا ضمن نطاق serverDomain
      // ومع ذلك، فإننا نفضل أن يكون لدينا مجال منفصل لكل طلب.
      // قم بإنشائه أولاً، وأضف req و res إليه.
      const reqd = domain.create()
      reqd.add(req)
      reqd.add(res)
      reqd.on('error', er => {
        console.error('Error', er, req.url)
        try {
          res.writeHead(500)
          res.end('Error occurred, sorry.')
        } catch (er2) {
          console.error('Error sending 500', er2, req.url)
        }
      })
    })
    .listen(1337)
})

domain.create()

الصنف: Domain

يُغلف صنف Domain وظائف توجيه الأخطاء والاستثناءات غير المُعالجة إلى كائن Domain النشط.

لِمعالجة الأخطاء التي يلتقطها، استمع إلى حدثه 'error' .

domain.members

مصفوفة من المؤقتات والباعثات للحدث أُضيفت صراحةً إلى المجال.

domain.add(emitter)

يضيف باعِثًا صراحةً إلى المجال. إذا ألقى أي مُعالِج للأحداث يُدعَى بواسطة الباعِث خطأً، أو إذا أصدر الباعِث حدثًا 'error'، فسيتم توجيهه إلى حدث 'error' الخاص بالمجال، تمامًا كما هو الحال مع الربط الضمني.

يعمل هذا أيضًا مع المؤقتات التي تُرجعها setInterval() و setTimeout(). إذا ألقى دالة الاستدعاء الخاصة بها خطأً، فسيتم التقاطه بواسطة مُعالِج 'error' الخاص بالمجال.

إذا كان المؤقت أو EventEmitter مرتبطًا بالفعل بمجال، فسيتم إزالته من ذلك المجال، ويتم ربطه بهذا المجال بدلاً من ذلك.

domain.bind(callback)

  • callback <Function> دالة الاستدعاء
  • مُخرجات: <Function> الدالة المُرتبطة

ستكون الدالة المُرجعّة غلافًا حول دالة الاستدعاء المُقدمة. عندما يتم استدعاء الدالة المُرجعّة، سيتم توجيه أي أخطاء يتم إلقاؤها إلى حدث 'error' الخاص بالمجال.

js
const d = domain.create()

function readSomeFile(filename, cb) {
  fs.readFile(
    filename,
    'utf8',
    d.bind((er, data) => {
      // إذا ألقى هذا خطأً، فسيتم تمريره أيضًا إلى المجال.
      return cb(er, data ? JSON.parse(data) : null)
    })
  )
}

d.on('error', er => {
  // حدث خطأ ما. إذا ألقينا به الآن، فسوف يُنهي البرنامج
  // برقم السطر ورسالة المكدس العاديين.
})

domain.enter()

طريقة enter() هي عملية أساسية تستخدمها طرق run() و bind() و intercept() لتعيين المجال النشط. وهي تقوم بتعيين domain.active و process.domain إلى المجال، وتدفع ضمنيًا المجال إلى مُكدس المجالات المُدار بواسطة وحدة المجال (راجع domain.exit() للحصول على تفاصيل حول مُكدس المجالات). تحدد دعوة enter() بداية سلسلة من المكالمات غير المتزامنة وعمليات الإدخال/الإخراج المرتبطة بمجال.

إنّ استدعاء enter() يُغيّر فقط المجال النشط، ولا يُغيّر المجال نفسه. يمكن استدعاء enter() و exit() عددًا تعسفيًا من المرات على مجال واحد.

domain.exit()

تخرج طريقة exit() من المجال الحالي، وتخرجه من مُكدس المجالات. في أي وقت ستنتقل فيه عملية التنفيذ إلى سياق سلسلة مختلفة من المكالمات غير المتزامنة، من المهم التأكد من الخروج من المجال الحالي. تحدد دعوة exit() إما نهاية أو انقطاع لسلسلة المكالمات غير المتزامنة وعمليات الإدخال/الإخراج المرتبطة بمجال.

إذا كانت هناك مجالات متداخلة متعددة مرتبطة بسياق التنفيذ الحالي، فستخرج exit() أي مجالات متداخلة ضمن هذا المجال.

إنّ استدعاء exit() يُغيّر فقط المجال النشط، ولا يُغيّر المجال نفسه. يمكن استدعاء enter() و exit() عددًا تعسفيًا من المرات على مجال واحد.

domain.intercept(callback)

  • callback <Function> دالة المُعالجة المُستدعاة
  • قيمة مُعادة: <Function> الدالة المُعترَضة

هذه الطريقة مُطابقة تقريبًا لـ domain.bind(callback). ومع ذلك، بالإضافة إلى التقاط الأخطاء المُلقاة، ستقوم أيضًا باعتراض كائنات Error المُرسلة كأول وسيطة إلى الدالة.

بهذه الطريقة، يمكن استبدال النمط الشائع if (err) return callback(err); بمعالج أخطاء واحد في مكان واحد.

js
const d = domain.create()

function readSomeFile(filename, cb) {
  fs.readFile(
    filename,
    'utf8',
    d.intercept(data => {
      // ملاحظة، لا يتم تمرير الوسيطة الأولى أبدًا إلى
      // دالة المُعالجة المُستدعاة نظرًا لافتراض أنها وسيطة 'Error'
      // وبالتالي يتم اعتراضها بواسطة المجال.

      // إذا كان هذا يُلقي خطأ، فسيتم تمريره أيضًا إلى المجال
      // بحيث يمكن نقل منطق معالجة الأخطاء إلى حدث 'error'
      // على المجال بدلاً من تكراره في جميع أنحاء
      // البرنامج.
      return cb(null, JSON.parse(data))
    })
  )
}

d.on('error', er => {
  // حدث خطأ ما. إذا قمنا بإلقائه الآن، فسوف يتسبب ذلك في تعطل البرنامج
  // مع رقم السطر ورسالة المكدس العاديين.
})

domain.remove(emitter)

  • emitter <EventEmitter> | <Timer> مُصدر الحدث أو المؤقت الذي سيتم إزالته من المجال

العكس من domain.add(emitter). يزيل معالجة المجال من مُصدر الحدث المحدد.

domain.run(fn[, ...args])

تشغيل الدالة المُقدمة ضمن سياق المجال، بربط جميع مُصدري الأحداث، والمؤقتات، والطلبات منخفضة المستوى التي تم إنشاؤها ضمن هذا السياق بشكل ضمني. اختياريًا، يمكن تمرير الوسائط إلى الدالة.

هذه هي أبسط طريقة لاستخدام مجال.

js
const domain = require('node:domain')
const fs = require('node:fs')
const d = domain.create()
d.on('error', er => {
  console.error('Caught error!', er)
})
d.run(() => {
  process.nextTick(() => {
    setTimeout(() => {
      // محاكاة بعض الأشياء غير المتزامنة المختلفة
      fs.open('non-existent file', 'r', (er, fd) => {
        if (er) throw er
        // المتابعة...
      })
    }, 100)
  })
})

في هذا المثال، سيتم تشغيل مُعالِج d.on('error')، بدلاً من تعطل البرنامج.

المجالات والوعود

اعتبارًا من Node.js 8.0.0، يتم تشغيل مُعالجات الوعود داخل المجال الذي تم فيه إجراء الاتصال بـ .then() أو .catch() نفسه:

js
const d1 = domain.create()
const d2 = domain.create()

let p
d1.run(() => {
  p = Promise.resolve(42)
})

d2.run(() => {
  p.then(v => {
    // قيد التشغيل في d2
  })
})

يمكن ربط مُعالِج الاستجابة بمجال معين باستخدام domain.bind(callback):

js
const d1 = domain.create()
const d2 = domain.create()

let p
d1.run(() => {
  p = Promise.resolve(42)
})

d2.run(() => {
  p.then(
    p.domain.bind(v => {
      // قيد التشغيل في d1
    })
  )
})

لن تتداخل المجالات مع آليات معالجة الأخطاء للوعود. بعبارة أخرى، لن يتم إصدار حدث 'error' للرفض غير المُعالَج للوعود.