التخلص من Callback hell بدون Promises

AbdullaScriptمنذ 7 سنوات

ماهو الcallback hell،  جحيم الكولباك؟

لا يوجد شيء مميز في الجافا سكربت يسمى بجحيم الكولباك ما هو الا تسميه لأمر ما قد يقع فيه المطور وهو طريقة كتابة كود حيث يحتوي على عدة دوال Asynchronous مترابطة مع بعضها، واحدة داخل الاخرى،  مثال:

 



getData1(function(x){
    getData2(x, function(y){
        getData3(y, function(z){ 
            ...
        });
    });
});

 

والسبب في ذلك يعود لأن ناتج "results" كل دالة يعتمد على ما سبقتها.

 

ملاحظة: كلمة asynchronous تعني أمر يحدث لاحقا << لا تركز على هالشي مو موضوعنا.

 

جحيم الكولباك أمر ليس بجيد للكود، خصوصا في الحالات المعقدة التي تكون فيها دوال كثيرة مربوطه مع بعضها البعض وهنا قد يؤخر وقت انتهاء من التنفيذ و كمطور انت لا تريد ان تقع في هذه المشكلة, لحل مشكلة جحيم الكولباك، يستخدم المطورون إحدى مكتبات البرومس promises "الوعد، الوعود"، فالوعد دالة إما أن يتم العمل به fulfilled مع اظهار نتيجة الوعد value او رده rejected مع إظهار السبب error، وقد يكون الكود يحتوي على أكثر من وعد، واحد تلو الآخر.

 

image1.png.cf5f8ffd9b153974432a834f5e8871e0.png

 

شخصيا لا اعتمد على الوعود في حل مشكلة جحيم الكولباك، عند التعامل مع قواعد البيانات MySQL رغم أنها الطريقة المثلى ولكن تتطلب الكثير مع الكود "وجهة نظر" و قد تواجهك مشاكل في أداء performance التطبيق بعد ذلك, استخدم طريقة أكثر بدائية و فعالة "تفي بالغرض" ، مبدأ الفكرة و طريقة عملها تتشابه كثيرا مع مبدأ عمل promises ولكن بكود أقل، سأرفق الطريقة مع المثال بالأسفل.

 

للتعرف أكثر على جحيم الكولباك، سأعرض لكم مثال على التعامل مع قاعدة البيانات MySQL بالـ NodeJS وعمل اكثر من query وناتج كل query يعتمد على كل سبقتها.


var express = require("express");
var app = express();
var mysql = require('mysql');

var conn = mysql.createConnection({
    host: 'localhost',
    user: 'me',
    password: 'secret',
    database: 'my_db'
});


conn.connect();

// GET method route
app.get('/user/posts', function (req, res) {

    /*

    http://www.example.com/user/posts?userId=26

    */

    var userId = req.query.userId;

    conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) {
        if (error) throw error;
        if (results.length) {

            var username = results[0].username;

            conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) {
                if (error) throw error;
                if (posts.length) {

                    conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) {
                        if (error) throw error;
                        if (statistics.length) {

                            res.json(200, {
                                posts: posts,
                                address: address
                            });

                        } else {

                            res.json(200, {
                                error: 'no statistics found'
                            });

                        }

                    });

                } else {

                    res.json(200, {
                        error: 'no posts found'
                    });

                }

            });

        } else {

            res.json(200, {
                error: 'no user found by ID 26'
            });

        }

    });

});

 

للتخلص على مثل هذه المشاكل في حالة التعامل مع MySQL / MariaDB يكون بإسناد results كل عملية query في دالة مثلا


    var express = require("express");
    var app = express();

    var mysql = require('mysql');
    var conn = mysql.createConnection({
        host: 'localhost',
        user: 'me',
        password: 'secret',
        database: 'my_db'
    });


    conn.connect();

    // GET method route
    app.get('/user/posts', function (req, res) {

        /*

        http://www.example.com/user/posts?userId=26

        */

        var userId = req.query.userId;

        conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) {
            if (error) throw error;
            if (results.length) {

                setUsername(results[0].username);

            } else {

                res.json(200, {
                    error: 'no user found by ID 26'
                });

            }

        });

        function setUsername(user) {
            var username = user;

            conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) {
                if (error) throw error;
                if (posts.length) {
                    setPosts(posts, username);
                } else {
                    res.json(200, {
                        error: 'no posts found'
                    });
                }
            });
        }

        function setPosts(po, user) {
            var posts = po; // po is the result of the prev query and it is an object it could be length of 1 or more doesn't matter
            var username = user;
            conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) {
                if (error) throw error;
                if (statistics.length) {
                    res.json(200, {
                        username: username,
                        posts: posts,
                        address: address
                    });

                } else {

                    res.json(200, {
                        error: 'no statistics found'
                    });

                }

            });
        }

    });

 

أو بطريقة اخرى وهي عمل execution لكل query بطريقة parallel او series  بإستخدام مكتبة async


/* 
Make Sure To Install async

npm install async --save

*/

var async = require(“async”);

// Parallel Method
async.parallel([
    wait5SecondsAndReturn1,
    wait5SecondsAndReturn2
], function (err, results){
    //after 5 seconds, results will be [1, 2]
});

function wait5SecondsAndReturn1(callback) {
    setTimeout(function(){
        callback(null, 1);
    }, 5000);
}

function wait5SecondsAndReturn2(callback) {
    setTimeout(function(){
        callback(null, 2);
    }, 5000);
}

//Series Method
async.series([
    wait5SecondsAndReturn1,
    wait5SecondsAndReturn2
], function (err, results){
    //after 10 seconds, results will be [1, 2]
});

function wait5SecondsAndReturn1(callback) {
    setTimeout(function(){
        callback(null, 1);
    }, 5000);
}

function wait5SecondsAndReturn2(callback) {
    setTimeout(function(){
        callback(null, 2);
    }, 5000);
}

// Another one
async.series([
  function(callback) {
    setTimeout(function() {
      console.log(“Task 1”);
      callback(null, 1);
    }, 300);
  },
  function(callback) {
    setTimeout(function() {
      console.log(“Task 2”);
      callback(null, 2);
    }, 200);
  },
  function(callback) {
    setTimeout(function() {
      console.log(“Task 3“);
      callback(null, 3);
    }, 100);
  }
], function(error, results) {
  console.log(results);
});

 

وبذلك تستطيع التخلص من جحيم الكولباك بدون promises التي يلقى الكثير من المبرمجين المبتدأئين صعوبة في التعامل مع هذا النوع من الدوال.

 

ملاحظات حول المثال السابق:

  • الأخذ بالاعتبار جميع الشروط من ان هذا المستخدم user لدية احصائية مسجله و بوستات, ان لم يكن تستطيع التخلص من الشرط if(posts.length) لا يهم , بالتالي سيتم تمرير اوبجكت بدون قيم.
  • بعض المبرمجين قد لا يتفق مع العملية, احترم ذلك ولكنها تفي بالغرض مع ES2015 و مايليه .. اسناد اوبجكت الى متغير يعطيه نفس قيمه الاوبجكت بشكل مباشر.
كلمات دليلية:
5
إعجاب
3631
مشاهدات
0
مشاركة
0
متابع
متميز
محتوى رهيب

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !