الفرق بين دالة غير متزامنة ووظيفة تقوم بإرجاع وعد

يوجد اختلاف بسيط ولكنه مهم بين دالة تُرجع ببساطة وعد ووظيفة تم إعلانها باستخدام async .

ألقِ نظرة على مقتطف الشفرة التالي:

 function fn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } async function asyncFn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } asyncFn().catch(err => console.error('Catched')) // => 'Catched' fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined 

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

هذا يعني أن JavaScript يضمن أن وظيفة asnycFn تُرجع وعدًا (إما ينجح أو يفشل) ، حتى لو حدث خطأ فيه ، في حالتنا فإن كتلة .catch() به.

ومع ذلك ، في حالة دالة fn ، لا يعرف .catch() بعد أن الوظيفة ستعود بوعد ، وبالتالي لن تصل الشفرة إلى كتلة .catch() ، ولن يتم اكتشاف الخطأ .catch() في وحدة التحكم.

مثال عن الحياة


أنا أعرف ما تفكر به الآن:
"متى بحق الجحيم سأرتكب مثل هذا الخطأ؟"

خمنت؟

حسنًا ، دعنا ننشئ تطبيقًا بسيطًا يفعل ذلك.

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

هنا هو ملف app.js لدينا:

 // app.js 'use strict' const express = require('express') const db = require('./db') const userModel = require('./models/user-model') const app = express() db.connect() app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) app.listen(3000, () => console.log('Server is listening')) 

نلقي نظرة فاحصة على كتلة .catch() ! هذا هو المكان السحري (لن) يحدث.

db.js استخدام ملف db.js للاتصال بقاعدة بيانات mongo:

 'use strict' const MongoClient = require('mongodb').MongoClient const url = 'mongodb://localhost:27017' const dbName = 'async-promise-test' const client = new MongoClient(url) let db module.exports = { connect() { return new Promise((resolve, reject) => { client.connect(err => { if (err) return reject(err) console.log('Connected successfully to server') db = client.db(dbName) resolve(db) }) }) }, getDb() { return db } } 

وأخيرًا ، لدينا ملف user-model.js فيه getUserById وظيفة getUserById واحدة فقط:

 // models/user-model.js 'use strict' const ObjectId = require('mongodb').ObjectId const db = require('../db') const collectionName = 'users' module.exports = { /** * Get's a user by it's ID * @param {string} id The id of the user * @returns {Promise<Object>} The user object */ getUserById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

إذا نظرت إلى ملف app.js ، فسترى ذلك عندما نذهب إلى localhost:3000/users/<id> getUserById localhost:3000/users/<id> نسميها الدالة getUserById المحددة في user-model.js ، لتمرير معلمة id user-model.js .

لنفترض أنك انتقلت إلى العنوان التالي: localhost:3000/users/1 . ما رأيك سيحدث بعد ذلك؟

حسنًا ، إذا أجبت: "سوف أرى خطأً كبيرًا من MongoClient" - لقد كنت على حق. لكي تكون أكثر دقة ، سترى الخطأ التالي: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters .

هل تعتقد أنه سيتم استدعاء كتلة .catch() في مقتطف الرمز التالي؟

 // app.js // ...  ... app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) // ...  ... 

لا. لن يتم استدعاؤه.

وماذا يحدث إذا قمت بتغيير إعلان دالة إلى هذا؟

 module.exports = { //  ,    async    ! async findById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

نعم ، عليك أن تبدأ في فهم ما هو ما. سيتم استدعاء كتلة .catch() ، وسنكون قادرين على معالجة الخطأ الذي تم اكتشافه .catch() للمستخدم.

بدلا من الاستنتاج


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

أردت فقط أن تعرف أنه في بعض الأحيان يمكن أن تحدث الوعود فرقًا كبيرًا ، وعندما تصادف الخطأ الذي تمت مناقشته في هذه المقالة (نعم ، لا "إذا") ، ستعرف السبب المحتمل لحدوثه.

ملاحظة PS perev.: على المقال الأصلي ، تم ترك تعليق مفيد من المستخدم Craig P Hicks ، والذي (بعد التعليقات في التعليقات) قررت أن أقتبس هنا:
أرغب في لفت الانتباه إلى أحد التفاصيل ، (في بيئة التطوير الخاصة بي) ، الأخطاء التي تحدث في نص Promise.resolve({<body>}) لا Promise.resolve({<body>}) " Promise.resolve({<body>}) ":

 Promise.resolve((()=>{throw "oops"; })()) .catch(e=>console("Catched ",e)); //  .catch()      "" 

لكن الأخطاء التي تحدث في نص new Promise() ( تقريبًا. الترجمة: في "الوعد المناسب" الأصلي " يتم اكتشافها":

 (new Promise((resolve,reject)=>{ resolve((()=>{throw "oops"})()) })) .catch(e=>console.log("Catched ",e)); // Catched oops 

ماذا عن هذا البيان:

 async function fn() { <body> } 

بمعنى أن هذا الخيار يعادل هذا:

 function fn() { return new Promise((resolve,reject)=>{ resolve({ <body> }) }) } 

وبالتالي ، فإن جزء الشفرة أدناه سيصطدم بالأخطاء إذا كان <body> لديه new Promise() ( تقريبًا. ترجم.: في "الوعد الأصلي" الأصلي:)

 function fn() { return Promise.resolve({<body}); } 

وبالتالي ، من أجل المثال من بداية المقالة إلى "التقاط" الأخطاء في كلتا الحالتين ، من الضروري إرجاع ليس Promise.resolve() ، ولكن new Promise() في الوظائف:

 function fn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } async function asyncFn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } asyncFn().catch(err => console.error("Catched")); // => 'Catched' fn().catch(err => console.error("Catched")); // => 'Catched' 

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


All Articles