عامل الويب أسهل مما كنت تعتقد

عامل الويب أسهل مما كنت تعتقد


في هذه المقالة ، سيتم وصف طريقة <em>eval</em> DIRTY وغير الآمنة وغير المستقرة <em>eval</em> . لذا ، إذا كنت غير مرتاح لذلك ، توقف عن القراءة الآن.


أولاً ، بقيت بعض المشكلات المتعلقة بالراحة دون حل: في الرمز الذي تم إرساله إلى عمال الويب ، لا يمكن استخدام الإغلاق.




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


ما الذي نحتاجه لجعل عمال الويب أكثر ملاءمة للعمل؟ أعتقد ، إنه ما يلي:


  • إمكانية إطلاق أي عامل في الويب على أي كود في أي لحظة
  • إمكانية إرسال بيانات معقدة للعاملين على الويب (مثيلات فئة ، وظائف)
  • إمكانية الحصول على وعد مع الرد من أحد العاملين على شبكة الإنترنت.

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


  • إضافة مكتبات / ملفات إلى عامل ويب
  • إطلاق.

الملف الذي سيكون داخل عامل الويب


قبل كتابة عامل الويب ، نحتاج إلى وصف ملف سيكون بداخله ، يدعم البروتوكول الموضح أعلاه. أنا أحب البرمجة الموجهة للكائنات (OOP) ، لذلك ستكون هذه الفئة باسم workerBody. يجب على هذه الفئة الاشتراك في حدث من النافذة الأصل.


 self.onmessage = (message) => { this.onMessage(message.data); }; 

الآن يمكننا الاستماع إلى الأحداث من النافذة الأصل. لدينا نوعان من الأحداث: تلك التي تنطوي على الرد والباقي. دعونا نفعل الأحداث: \
تتم إضافة المكتبات والملفات إلى أحد العاملين على الويب باستخدام importScripts API.


والآن الجزء الأكثر رعبا: لإطلاق وظيفة عشوائية ، سنستخدم eval .


 ... onMessage(message) { switch (message.type) { case MESSAGE_TYPE.ADD_LIBS: this.addLibs(message.libs); break; case MESSAGE_TYPE.WORK: this.doWork(message); break; } } doWork(message) { try { const processor = eval(message.job); const params = this._parser.parse(message.params); const result = processor(params); if (result && result.then && typeof result.then === 'function') { result.then((data) => { this.send({ id: message.id, state: true, body: data }); }, (error) => { if (error instanceof Error) { error = String(error); } this.send({ id: message.id, state: false, body: error }); }); } else { this.send({ id: message.id, state: true, body: result }); } } catch (e) { this.send({ id: message.id, state: false, body: String(e) }); } } send(data) { data.body = this._serializer.serialize(data.body); try { self.postMessage(data); } catch (e) { const toSet = { id: data.id, state: false, body: String(e) }; self.postMessage(toSet); } } 

طريقة onMessage هي المسؤولة عن تلقي رسالة واختيار معالج ، doWork تطلق وظيفة المرسلة ويرسل يرسل ردا على الإطار الأصل.


المحلل اللغوي والمسلسل


والآن بعد أن أصبح لدينا محتوى عامل الويب ، نحتاج إلى تعلم إجراء تسلسل وتحليل أي بيانات ، بحيث يمكن إرسالها إلى عامل الويب. لنبدأ مع المسلسل. نريد أن نكون قادرين على إرسال أي بيانات إلى عامل الويب ، بما في ذلك مثيلات الفصول الدراسية والدروس والوظائف ، بينما تتيح القدرة الأصلية للعامل على الويب إرسال البيانات المشابهة فقط لـ JSON. للتغلب على ذلك ، سنحتاج _ eval _. سنقوم بجمع جميع البيانات التي لا يمكن لـ JSON قبولها في هياكل اللدغة المقابلة وإطلاقها على الجانب الآخر. للحفاظ على القابلية للتغير ، سيتم استنساخ البيانات المستلمة على الفور ، لتحل محل أي شيء لا يمكن تسلسله بالطرق العادية مع كائنات الخدمة ، والتي سيتم استبدالها على الجانب الآخر بمحلل. للوهلة الأولى ، هذه المهمة ليست صعبة ، ولكن هناك العديد من المزالق. أخطر قيود هذا النهج هو عدم القدرة على استخدام الإغلاق ، الأمر الذي يؤدي إلى نمط كتابة رمز مختلف قليلاً. لنبدأ مع أسهل جزء ، وظيفة. أولاً ، نحتاج أن نتعلم كيف نميز وظيفة عن مُنشئ الفصل. لنقم بذلك:


 static isFunction(Factory){ if (!Factory.prototype) { // Arrow function has no prototype return true; } const prototypePropsLength = Object.getOwnPropertyNames(Factory.prototype) .filter(item => item !== 'constructor') .length; return prototypePropsLength === 0 && Serializer.getClassParents(Factory).length === 1; } static getClassParents(Factory) { const result = [Factory]; let tmp = Factory; let item = Object.getPrototypeOf(tmp); while (item.prototype) { result.push(item); tmp = item; item = Object.getPrototypeOf(tmp); } return result.reverse(); } 

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


عندما نكتشف وظيفة ، فإننا نستبدلها فقط بكائن خدمة مع الحقول __type = "دالة متسلسلة" والقالب يتوافق مع قالب هذه الوظيفة (func.toString ()).


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


 static isInstance(some) { const constructor = some.constructor; if (!constructor) { return false; } return !Serializer.isNative(constructor); } static isNative(data) { return /function .*?\(\) \{ \[native code\] \}/.test(data.toString()); } 

نحن نعتقد أن الكائن منتظم إذا لم يكن له مُنشئ أو كان منشئه وظيفة أصلية. بمجرد اكتشاف مثيل فئة ، سنحل محله كائن خدمة مع الحقول التالية:


  • __type: "مثيل تسلسلي"
  • البيانات هي البيانات الواردة في المثيل
  • الفهرس هو فهرس الفئة لهذا المثيل في قائمة فئة الخدمة.

لإرسال البيانات ، يتعين علينا إنشاء حقل إضافي ، حيث سنقوم بتخزين قائمة بالفئات الفريدة التي نرسلها. ومع ذلك ، هناك تحدٍ: اكتشاف فئة ، نحتاج إلى عدم اتخاذ القوالب الخاصة بها فحسب ، بل أيضًا قوالب جميع الفئات الرئيسية وحفظها كصفوف مستقلة ، بحيث يتم إرسال كل فئة رئيسية مرة واحدة فقط ، مع توفير دليل على المثيل. من السهل اكتشاف فئة: هذه وظيفة فشلت في إثبات Serializer.isFunction. عند إضافة فئة ، نتحقق من وجود هذه الفئة في قائمة البيانات المتسلسلة ونضيف فئات فريدة فقط. الكود الذي يجمع فئة في قالب كبير جدًا ومتوفر هنا .


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


خلق عامل الويب


لكي يعمل عامل الويب بنجاح ، نحتاج إلى محلل ومسلسل داخل عامل الويب وخارجه. لذلك نأخذ المسلسل ونحوله ، المحلل اللغوي وجسم عامل الويب إلى قالب. من القالب ، نقوم بإنشاء نقطة وننشئ رابط تنزيل عبر URL.createObjectURL (قد لا تعمل هذه الطريقة مع بعض "سياسة أمان المحتوى"). هذه الطريقة جيدة أيضًا لإطلاق رمز عشوائي من سلسلة.


 _createworker(customworker) { const template = `var Myworker = ${this._createTemplate(customworker)};`; const blob = new Blob([template], { type: 'application/javascript' }); return new worker(URL.createObjectURL(blob)); } _createTemplate(workerBody) { const Name = Serializer.getFnName(workerBody); if (!Name) { throw new Error('Unnamed worker Body class! Please add name to worker Body class!'); } return [ '(function () {', this._getFullClassTemplate(Serializer, 'Serializer'), this._getFullClassTemplate(Parser, 'Parser'), this._getFullClassTemplate(workerBody, 'workerBody'), `return new workerBody(Serializer, Parser)})();` ].join('\n'); } 

نتيجة


لذلك ، لدينا مكتبة بسيطة الاستخدام يمكنها إرسال أي رمز إلى عامل الويب. يدعم فئات TypeScript ، على سبيل المثال:


 const wrapper = workerWrapper.create(); wrapper.process((params) => { // This code in worker. Cannot use closure! // do some hard work return 100; // or return Promise.resolve(100) }, params).then((result) => { // result = 100; }); wrapper.terminate() // terminate for kill worker process 

التنمية المستقبلية


لسوء الحظ ، هذه المكتبة بعيدة عن المثالية. نحتاج إلى إضافة دعم من أدوات التثبيت والحروف للفئات والكائنات والنماذج الأولية والميزات الثابتة. أيضًا ، نحتاج إلى إضافة التخزين المؤقت ، وهو إطلاق نص بديل دون eval ، باستخدام URL.createObjectURL بدلاً من ذلك. أخيرًا ، يجب إضافة ملف به محتوى عامل الويب إلى التجميع (في حالة عدم توفر الإنشاء أثناء التنقل) إلخ. زيارة مستودع !

Source: https://habr.com/ru/post/ar462325/


All Articles