مقدمة
كانت المرة الأولى التي تلامس فيها على اتصال مع Promise عندما أصدرت Microsoft نظام التشغيل Windows 8 في عام 2012 ودرست باستخدام HTML5 لكتابة تطبيقات المترو مع موقف فضولي. في ذلك الوقت ، كانت الواجهات غير المتزامنة في مكتبة WinJS المقدمة مع HTML5 في شكل وعد ، والتي كانت ببساطة كتاب السماء بالنسبة لي الذي تخرج للتو من JavaScript في ذلك الوقت. ما كنت أفكر فيه في ذلك الوقت هو أن Microsoft كانت تتلاعب به مرة أخرى.
بشكل غير متوقع ، بحلول عام 2015 ، تم كتابة الوعد بالفعل في معيار ES6. علاوة على ذلك ، يوضح الاستطلاع أن مبرمجي JS يستخدمون هذا الشيء مرتفعًا جدًا.
ومن المفارقات ، مثل Microsoft ، التي استخدمت على نطاق واسع في واجهة تطوير تطبيقات المترو في وقت مبكر من عام 2012 ، لا يزال متصفحها الخاص ، أي لم يدعم الوعد حتى مات في عام 2015. يبدو أن Microsoft لا تملك هذه التكنولوجيا ، لكنها تخلت عن العلاج لـ IE. . .
إذا نظرنا إلى الوراء الآن ، كان الشيء الأكثر إزعاجًا في رؤية الوعد في ذلك الوقت هو أن المبتدئين يبدو رائعًا وأيضًا الميزة الأكثر مدحًا من قبل مبرمجي JS: ثم سلسلة استدعاء الوظائف.
ثم سلسلة استدعاء الوظيفة ، في الأساس ، هي استدعاء عمليات غير متزامنة متعددة بالتسلسل. تبدأ هذه المقالة من هذه النقطة وتدرس وتتعلم ميزة الوعد.
وعد حل
ضع في اعتبارك السيناريو التالي ، بعد تأخير الوظيفة بمقدار ثانيتين ، قم بطباعة خطًا من السجلات ، ثم يتخلف بنسبة 3 ثوان ، ثم يتأخر بمقدار 4 ثوانٍ ، بطباعة خطًا من السجلات. هذا شيء بسيط للغاية في لغات البرمجة الأخرى ، ولكن من الصعب الوصول إلى JS ، ومن المحتمل أن يتم كتابة الرمز على النحو التالي:
var myfunc = function () {setTimeOut (function () {console.log ("log1") ؛ setTimeOut (function () {console.log ("log2") ؛ setTimeout (function () {console.log ("log3") ؛} ، 4000) ؛بسبب هيكل رد الاتصال متعدد الطبقات المتداخلة ، يتم تشكيل بنية هرم نموذجية هنا. إذا كان منطق العمل أكثر تعقيدًا ، فسيصبح جحيم رد اتصال مرعب.
إذا كان لديك وعي أفضل ومعرفة كيفية استخراج وظائف بسيطة ، فسيبدو الرمز هكذا:
var func1 = function () {setTimeOut (func2 ، 2000) ؛} ؛ var func2 = function () {console.log ("log1") ؛ setTimeOut (func3 ، 3000) ؛} ؛ var func3 = function () {console.log ("log2") ؛ setTimeOut (func4 ، 4000) ؛} ؛ var func4 = function () {console.log ("log3") ؛} ؛هذا يبدو أفضل قليلاً ، لكنه دائمًا ما يكون غريبًا بعض الشيء. . . حسنًا ، في الواقع ، فإن مستوى JS محدود ، لذلك لا يمكنني القول لماذا لا يمكنني كتابة هذا جيدًا. إذا كنت تعرف لماذا هذا ليس جيدًا ، لذا اخترعت الوعد ، فيرجى إخبارنا بذلك.
الآن دعنا نعود إلى النقطة ونتحدث عن شيء الوعد.
وصف الوعد
واسمحوا لي أن أقتبس وصف MDN للوعد هنا:
يتم استخدام كائن الوعد للحسابات المؤجلة والحسابات غير المتزامنة. يمثل كائن الوعد عملية لم تكتمل ولكن من المتوقع أن تكتمل في المستقبل.
كائن الوعد هو وكيل لقيمة الإرجاع ، والتي قد لا تكون معروفة عند إنشاء كائن الوعد. يتيح لك تحديد طريقة التعامل للنجاح أو فشل عملية غير متزامنة. يتيح ذلك طريقة غير متزامنة لإرجاع قيمة مثل طريقة متزامنة: تقوم الطريقة غير المتزامنة بإرجاع كائن الوعد الذي يحتوي على قيمة الإرجاع الأصلية بدلاً من قيمة الإرجاع الأصلية.
يحتوي كائن الوعد على الحالات التالية:
• معلق: الحالة الأولية ، غير مملوءة أو رفض.
• الوفاء: العملية الناجحة.
• رفض: فشل التشغيل.
يمكن تحويل كائن الوعد مع حالة معلقة إلى حالة تم الوفاء بها بقيمة نجاح أو حالة مرفوضة برسالة فشل. عندما تنتقل الحالة ، سيتم استدعاء الطريقة التي تعد بها الوعد. ثم يتم استدعاء (مقبض الوظيفة). (عند ربط طريقة ما ، إذا كان كائن الوعد موجودًا بالفعل في الحالة التي تم الوفاء بها أو المرفوضة ، فسيتم استدعاء الطريقة المقابلة على الفور ، لذلك لا يوجد شرط سباق بين الانتهاء من العملية غير المتزامنة وطريقة الربط الخاصة به.)
لمزيد من الأوصاف وأمثلة من الوعد ، يرجى الرجوع إلى إدخال الوعد من MDN أو إدخال الوعد من MSDN.
حاول حل مشكلتنا مع الوعد
استنادًا إلى الفهم أعلاه للوعد ، نعلم أنه يمكننا استخدامه لحل المشكلة التي تفيد بأن الكود وراء عمليات الاسترجاعات المتعددة الطبقات المتداخلة غبية ويصعب الحفاظ عليها. الرابطان المذكوران أعلاه واضحان بالفعل حول بناء الجملة ومعلمات الوعد. لن أكررهم هنا ، فقط قم بتحميل الرمز.
دعنا أولاً نجرب حالة بسيطة نسبيًا ، والتي تنفذ فقط التأخير والاسترجاعات مرة واحدة:
وعد جديد (دالة (res ، rej) {console.log (date.now () + "start setTimeout") ؛ setTimeout (res ، 2000) ؛}). ثم (function () {console.log (date.now () +يبدو أنه لا يوجد فرق عن الأمثلة في MSDN ، ونتيجة التنفيذ هي كما يلي:
$ node promistest.js1450194136374 Start SetTimeOut1450194138391 Timeout Call
لذلك إذا أردنا القيام بتأخير آخر ، فيمكنني كتابة هذا:
وعد جديد (دالة (res ، rej) {console.log (date.now () + "start setTimeout 1") ؛ setTimeout (res ، 2000) ؛}) 3000) ؛}).يبدو أنه يعمل بشكل صحيح أيضًا:
$ node promistest.js1450194338710 Start SetTimeout 11450194340720 timeout 1 Call Back1450194340720 Start SetTimeout 21450194343722 timeout 2 call back
لكن الكود يبدو غبيًا ولطيفًا ، أليس كذلك؟ إنه بناء غامض هرم مرة أخرى. هذا يتعارض مع الغرض من تقديم الوعد.
إذن ما هي المشكلة؟ ما هو الموقف الصحيح؟
يتم إخفاء الإجابة في قيمة الإرجاع للدالة آنذاك ودالة رد الاتصال Onfulfilled (أو OnCompleted).
بادئ ذي بدء ، ستعيد وظيفة THE THENT متغير وعد جديد ، ويمكنك استدعاء وظيفة متغير الوعد الجديد هذا مرة أخرى ، مثل هذا:
وعد جديد (...). ثم (...). ثم (...). ثم (...). ثم (...). ثم (...).
يعتمد نوع Promies الذي يتم إرجاعه بواسطة الوظيفة آنذاك على قيمة الإرجاع لاستدعاء رد الاتصال.
في الواقع ، يمكن لـ Onfulfilled إرجاع متغير عادي أو متغير وعد آخر.
إذا قام Onfulfilled بإرجاع القيمة العادية ، فستقوم الوظيفة بإرجاع متغير الوعد الافتراضي. إن تنفيذ وظيفة هذا الوعد آنذاك سيجعل الوعد يرضي على الفور ، ويتم تنفيذ وظيفة onfulfilled ، ومعلمة الإدخال onfuliled هي قيمة إرجاع onfulfilled السابقة.
إذا قام Onfulfilled بإرجاع متغير الوعد ، فسيتم استخدام متغير الوعد هذا كقيمة الإرجاع للدالة ثم.
لا تحتوي المستندات الموجودة على MDN و MSDN على وصف إيجابي واضح لهذه السلسلة من الإعدادات لوظيفة آنذاك ودالة onfulfiled. أما بالنسبة للوثيقة الرسمية لـ ES6 ECMASCRIPT 2015 (الإصدار السادس ، ECMA-262). . . لا أستطيع حقًا فهم مستواي. إذا كان بإمكان أي خبير أن يشرح وصف قيمتي الإرجاع في المستند الرسمي ، فيرجى ترك رسالة للحصول على المشورة! ! !
لذا فإن ما سبق هو لعبتي الحرة ، ومن الصعب وصف منظمة اللغة. سوف تفهم بعد قراءة الكود.
أولاً ، حالة إرجاع المتغيرات العادية:
وعد جديد (دالة (res ، rej) {console.log (date.now () + "start setTimeout 1") ؛ setTimeout (res ، 2000) ؛}) arg) ؛نتيجة تنفيذ الرمز أعلاه هي:
$ Node Promistest.js145027122125 Start SetTimeout 1145027124129 Timeout 1 Call Back145027124129 Last Onfullized Return 1024
إنه أمر مثير للاهتمام ، صحيح ، لكن هذا ليس المفتاح. المفتاح هو أن الدالة onfulfilled تُرجع متغير الوعد ، مما يجعل من المريح بالنسبة لنا استدعاء عمليات غير متزامنة متعددة على التوالي. على سبيل المثال ، يمكننا محاولة القيام بعمليتين للتأخير على التوالي:
وعد جديد (دالة (res ، rej) {console.log (date.now () + "start setTimeout 1") ؛ setTimeout (res ، 2000) ؛}). 3000) ؛}) ؛}).نتائج التنفيذ هي كما يلي:
$ node promistest.js1450277510275 Start SetTimeout 11450277512276 timeout 1 Call Back1450277512276 Start SetTimeout 21450277515327 timeout 2 call back
إذا كنت تعتقد أن هذا ليس رائعًا ، فليس من مشكلة القيام بذلك عدة مرات:
وعد جديد (دالة (res ، rej) {console.log (date.now () + "start setTimeout 1") ؛ setTimeout (res ، 2000) ؛}). 3000) ؛}) ؛}) "Timeout 3 Call مرة أخرى") ؛$ node promistest.js1450277902714 Start SetTimeout 11450277904722 timeout 1 Call Back145027904724 Start SetTimeout 214502779077725 timeout 2 Call Back145027790725 Start timeout 314502779103030 SetTimeout 41450277916744 Timeout 4 Call
يمكن ملاحظة أن وظائف رد الاتصال المتعددة المتأخرة يتم ترتيبها بطريقة منظمة ، ولا يوجد هيكل شبيه بالهرم. على الرغم من أن الكود يستدعي العمليات غير المتزامنة ، يبدو أنها تتكون جميعًا من عمليات متزامنة. هذا هو فائدة الوعد الذي يجلب لنا.
إذا كان لديك عادة جيدة في تقطير رمز المطوّل في وظائف منفصلة ، فسيكون ذلك أكثر جمالًا:
TimeOut1 () {return New Promise (function (res ، rej) {console.log (date.now () + "start timeout1") ؛ setTimeOut (res ، 2000) ؛}) ؛ timeout3 () {return New Promise (function (res ، rej) {console.log (date.now () + "timeout2") ؛ setTimeOut (res ، 3000) ؛}) ؛ TimeOut4 () {return New Promise (function (res ، rej) {console.log (date.now () + "start timeout4") ؛ setTimeOut (res ، 5000) ؛}) ؛} timeout1 () .Then (timeout2) .Then (timeout3) .Then (timeout4) .then (function () {console. }) ؛$ node promistest.js1450278983342 timeout11450278985343 Start timeout2145027898351 Start timeout31450278992356 Starting41450278997370 timout4 callback
بعد ذلك ، يمكننا الاستمرار في دراسة مشكلة تمرير المعلمات الواردة لوظيفة Onfuliled.
نحن نعلم بالفعل أنه إذا كانت الدالة onfulfilled السابقة تُرجع قيمة عادية ، فإن هذه القيمة هي معلمة الإدخال للدالة onfulfiled ؛ ثم إذا قامت Onfulfilled السابقة بإرجاع متغير الوعد ، فمن أين تأتي معلمة الإدخال الخاصة بـ Onfuliled؟
الجواب هو أن معلمة الإدخال هذه الدالة onfulfiled هي القيمة التي تم تمريرها عند استدعاء وظيفة حل في الوعد السابق.
لم أستطع قبول القفزة لفترة من الوقت ، أليس كذلك؟ دعونا نفعل ذلك بشكل جيد.
بادئ ذي بدء ، ما هو وظيفة الوعد. باستخدام بيان من Zou Zou فوق MDN
حل كائن الوعد مع قيمة قيمة النجاح. إذا كانت القيمة مستمرة (يمكن ذلك ذلك ، أي مع الطريقة التي تنفذها) ، فإن كائن الوعد الذي تم إرجاعه "سيتبع" القيمة
باختصار ، هذا هو رد الاتصال عندما تكون المكالمة غير المتزامنة ناجحة.
دعونا نلقي نظرة على شكل رد الاتصال في واجهة غير متزامنة عادية. خذ fs.readfile (ملف [، خيارات] ، رد اتصال) على nodejs على سبيل المثال. مثال الاتصال النموذجي على النحو التالي
fs.ReadFile ('/etc/passwd' ، function (err ، data) {if (err) error ؛ console.log (data) ؛}) ؛لأنه بالنسبة لوظيفة FS.ReadFile ، سواء كانت ناجحة أو فاشلة ، فإنها ستستدعي رد الاتصال على وظيفة رد الاتصال ، لذلك يقبل رد الاتصال هذا معلمتين ، وهما وصف الاستثناء على ERR وبيانات نتيجة الإرجاع على النجاح.
لذلك إذا استخدمنا وعدًا لإعادة بناء هذا المثال لقراءة الملف ، فكيف يجب أن نكتبه؟
أولاً ، تغليف وظيفة FS.ReadFile:
الوظيفة ReadFile (filename) {return New Promise (function (حل ، رفض) {fs.readfile (filename ، function (err ، data) {if (err) {refect (errect (err) ؛} else {desolve (data) ؛}}) ؛}) ؛}) ؛}}والثاني هو المكالمة:
ReadFile ('thefile.txt'). ثم (الدالة (البيانات) {console.log (data) ؛} ، function (err) {throw err ؛}) ؛تخيل أين يتم وضع محتوى الملف عادة في واجهة المكالمات المتزامنة لقراءة الملفات بلغات أخرى؟ هل قيمة إرجاع الوظيفة صحيحة؟ الجواب خارج ، ما هو دخول الجينسنغ لهذا العزم؟ إنها قيمة الإرجاع عندما تكون المكالمة غير المتزامنة ناجحة.
مع هذا المفهوم ، ليس من الصعب فهم "معلمة الإدخال للدالة onfulfiled هي القيمة التي تم تمريرها عند استدعاء وظيفة حل في الوعد السابق". نظرًا لأن المهمة المليئة بالملاحظة هي معالجة النتيجة بعد نجاح المكالمة غير المتزامنة السابقة.
للأسف أخيرًا. . .
لخص
يرجى السماح لي باستخدام قطعة من الكود لتلخيص النقاط الرئيسية الموضحة في هذه المقالة:
دالة callp1 () {console.log (date.now () + "start callp1") ؛ إرجاع وعد جديد (دالة (res ، rej) {setTimeOut (res ، 2000) ؛}) ؛} callp2 () {console.log (date.now () + "start callp2") ؛ إرجاع الوعد الجديد (الدالة (res ، rej) {setTimeOut (function () {res ({arg1: 4 ، arg2: "arg2 value"}) ؛} ، 3000) ؛}) ؛} وظيفة callp3 (arg) {console.log (date.now ()) + إرجاع وعد جديد (الدالة (res ، rej) {setTimeOut (function () {res ("callp3") ؛} ، arg * 1000) ؛}) ؛} callp1 (). ثم (الدالة () {console.log (date.now () + ret value = " + json.stringify (ret)) ؛ return callP3 (ret.arg1) ؛}). ثم (الدالة (ret) {console.log (date.now () +" callp3 return with ret value = " + ret) ؛}) $ node promistest.js1450191479575 ابدأ callp11450191481597 callp1 return1450191481599 ابدأ callp2145019191484605 callp2 مع ret value = {"arg1": 4 ، "arg2" 41450191488610 CallP3 Return مع RET Value = CallP3إن تجربة التعلم البسيطة المذكورة أعلاه لاستخدام الوعد لحل المكالمات غير المتزامنة متعددة الطبقات هي كل المحتوى الذي أشاركه معك. آمل أن تتمكن من إعطائك مرجعًا وآمل أن تتمكن من دعم wulin.com أكثر.