کال بک فانکشن و برنامه نویسی async (ناهمزمان) در جاوااسکریپت

10 اردیبهشت 1400

کال بک فانکشن ها به چه معنا هستند؟ higher-order functions به چه معنا هستند؟ وقتی بخواهیم در جاوا اسکریپت برنامه نویسی ناهمزمان انجام دهیم چه کنیم؟ مگر جاوا اسکریپت sync نیست؟ چگونه async میشود؟ اینجا ترجمه دایکومنتیشن سایت رسمی ند جی اس را میخوانید.

جاوا اسکریپت یک زبان همزمان async است. در آن واحد فقط میتواند یک کار را انجام دهد. نمیتواند چند کار را به موازات هم پیش ببرد.

کد ها بصورت سری و به دنبال هم اجرا میشوند.

const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()

اما جاوا اسکریپت در بروزر متولد شد. اولین کارش این بود که به اکشن های کاربر پاسخ دهد. مثلا onClick, onMouseOver, onChange, onSubmit ! بنظر شما انجام این ایونت ها در بروزر چگونه ممکن بود؟ جاوا اسکریپت همواره async کار میکرد؟

پاسخ به محیط بروزر برمیگردد. محیط بروزر API هایی برای اجرای این کاربرد ارائه میدهد.

اخیرا Nodejs نیز با ارائه محیط non-blocking I/O (غیرمسدود کننده ورودی/خروجی) این مفهوم ناهمزمانی را برای درخواست های شبکه، دسترسی به فایل ها و … توسعه داد.

کال بک فانکشن Callback

شما نمیتوانید حدث بزنید که کاربر چه زمانی روی یک دکمه کلیک میکند. بنابراین یک event handler تعریف میکنید تا ایونت کلیک را مدیریت کند. این ایونت هندلر یک تابع میپذیرد تا در زمان رخ دادن ایونت آن تابع را اجرا کند.

const Btn = document.getElementById('button');

Btn.addEventListener('click', () => {
  //item clicked
})

به آن کال بک Callback میگویند.

کال بک یک فانکشن است که به عنوان مقدار ورودی به تابعی دیگر داده میشود، و فقط زمانی اجرا میشود که ایونت رخ بدهد. اگر ما میتوانیم این کار را انجام دهیم، دلیلش این است که جاوا اسکریپت دارای توابع first-class است، که اجازه میدهند کال بک ها را داخل خودشان داشته باشند ( به آنها higher-order functions هم میگویند.)

معمولا تمام کدهای سمت کاربر را داخل ایونت لیستنر load قرار میدهند که مربوط به ابجکت window است. (کدهای زیر) به این ترتیب کال بک فقط زمانی که صفحه به صورت کامل بارگیری شد در دسترس خواهد بود.

window.addEventListener('load', () => {
  //window loaded
  //do what you want
})

کال بک فانکشن ها همه جا کاربرد دارند! فقط در DOM نیستند.

یک نمونه کاربرد آنها در تایمر setTimeout را ببینید:

setTimeout(() => {
  // runs after 2 seconds
}, 2000)

کاربرد دیگر کال بک فانکشن ها در درخواست های XHR است. در مثال زیر وقتی state تغییر کند (onreadystatechange) یک کال بک اجرا خواهد شد.

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
  }
}
xhr.open('GET', 'https://yoursite.com')
xhr.send()

مدیریت ارور ها در کال بک

شما چگونه ارور ها را مدیریت میکنید؟ یکی از روش های مرسوم در Node.js استفاده از اولین پارامتر ورودی در کال بک است که ابجکت ارور را برمیگرداند.

اگر ارور نداشته باشد NULL برمیگرداند. اگر هم ارور داشته باشد توضیحاتی درباره ارور بازمیگرداند.

fs.readFile('/file.json', (err, data) => {
  if (err !== null) {
    //handle error
    console.log(err)
    return
  }

  //no errors, process data
  console.log(data)
})

مشکلات Callback ها

توابع Callback برای نمونه های کوچک خوب هستند.

از آنجا که هر کال بک یک سطح تو در تو ایجاد میکند، اگر شما تعداد زیادی کال بک داشته باشید انگاه کدهای پیچیده ی تو در تو خواهید داشت:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        //your code here
      })
    }, 2000)
  })
})

در کدهای بالا فقط 4 سطح تو در تو را مشاهده میکنید. فرض کنید پروژه شما بزرگ باشد و بسیار پیچیده تر از کدهای بالا باشد.

انگاه چه کار میخواهید بکنید؟

جایگزین کال بک ها

با معرفی ES6 ، جاوا اسکریپت ویژگی های متعددی را معرفی کرد که به async شدن کمک کرد، بدون اینکه نیازی به Callback باشد: Promises (ES6) و Async/Await (ES2017) .