تعداد بازدید: 2962

مدیریت پردازش های ناهمگام با Promise – قسمت یک

نتیجه هر پردازش صحت یا عدم صحت است . همچنین در فرآیند اجرای برنامه پردازش های Asynchronous نا معلوم است . برای مثال پردازش های Ajax به صورت نا همگام اجرا و غیر قابل پیش بینی در جریان اجرای برنامه است.

کاربرد Callback ها و Promise ها برای پردازش های نا همگام (Asynchronous) می باشد . به طور مثال دو پردازش به صورت همزمان اجرا می گردد. ممکن از یک پردازش بعد از ۱۰۰ میلی ثانیه به اتمام برسد و پردازش دیگر بعد از ۲۰۰ میلی ثانیه . در برخی پردازش ها که اتصال به سرور می باشد – Ajax – این زمان قابل پیش بینی نیست. این پردازش ها از جریان برنامه خارج است و ما نمی توانیم به صورت عادی پردازش ها را مدیریت کنیم.

ما دو نوع پردازش در علوم کامپیوتر داریم :

  • Asynchronous یا پردازش ناهمگام : در این نوع پردازش زمانی که شما یک پردازش را اجرا می کنید بدون توجه به این که پردازش اول تمام شده است یا نه به پردازش دوم می روید و عملا پردازش ها به صورت غیر همزمان تمام می شوند ( بدون ترتیب ) به این دسته از پردازش ها ناهمگام می گوییم.
  • synchronous یا پردازش همگام : در این نوع پردازش که به آن ها Blocking نیز می گویند تا اتمام پردازش اول پردازش دوم انجام نمی شود. به طور مثال حلقه دوم از یک loop تا اتمام حلقه اول شروع نمی شود.

توضیحات کامل در رابطه با پردازش های همگام و ناهمگام و همچنین Blocking/None Blocking را حتما مطالعه بفرمایید.

Callback Functions

در این نوع توابع ما می توانیم یک تابع را به عنوان ورودی تابع خود بگیریم و جایی که مطمئن شدیم که تابع اصلی اجرا شده است تابع ورودی را اجرا کنیم که در این صورت ماهیت تابع ابتدایی به callback تغییر می کند.

به طور مثال در تابع زیر می خواهیم که یک تابع پس تابع دیگری اجرا گردد. اجرای تابع اول ۲ ثانیه زمان می برد و اجرای تابع دوم کمتر از ۱ میلی ثانیه . به این ترتیب اگر به صورت عادی آن ها را اجرا کنیم ابتدا تابع دوم خروجی می دهد و بعد تابع اول.

function doSomething() {
      setTimeout(() => {
            console.log('do something');
      }, 2000);
}

function foo() {
      console.log('foo'); 
}

doSomething();
foo();

خروجی کد بالا به شکل ابتدا عبارت foo و پس از دو ثانیه عبارت do something نمایش داده می شود . اگر بخواهیم ترتیب اجرا را تغییر دهیم نیاز به callback داریم. برای این منظور باید تابع foo را ملزم به اجرای تمام و کمال تابع doSomething کنیم.

function doSomething(callback) {
      setTimeout(() => {
            console.log('do something');
            callback();
      }, 2000);
}

function foo() {
      console.log('foo');
}

doSomething(foo);

در کد بالا نقطه ای که ما مطمئن شدیم که تابع اصلی اجرا می گردد درون تابع SetTimeout می باشد. این نقطه با توجه به ماهیت توابع می تواند متفاوت باشد . به طور مثال اگر یک اسکریپت لود شد نقطه اطمینان از اتمام تابع چک کردن اسکریپت در صفحه است.

هرم ضلالت یا Pyramid of Doom 🙂

این به ازای حالتی است که قرار است تعداد زیادی پردازش ناهمگام به صورت زنجیره ای به یک پردازش ناهمگام متصل گردد که ممکن است بقیه پردازش ها نیز درخواست هایی از جنس Asynchronous باشند.

به طور مثال فرض کنید که قرار است یک درخواست به سرور بدهیم و لیست کاربران را همراه شناسه هایشان برگرداند. و درگام بعد باید درخواست دوم شناسه های دریافت شده از کاربران را در درخواست اول دریافت کردیم در درخواست بعدی ارسال کنیم و در این جا لیست پست های این کاربران را دریافت کنیم. می بینید که این اتصال به صورت پشت سر هم می باشد و ممکن است اتصال بعدی هم در کار باشد (مثلا دریافت دسته بندی ها به صورت Hierarchical )

مثال بعدی می تواند درخواست های Comet باشد که یک درخواست به سرور می دهیم (با پاسخ طولانی مدت) و منتظر وضعیت comet می مانیم و در نهایت اگر وضعیت comet تغییر کرد باید به ازای آن درخواست های بعدی را ارسال کنیم که یک زنجیره ای از درخواست ها در این مورد اتفاق می افتد که استفاده از Callback در آن syntax بسیار گیج کننده ای دارد.

AsyncRequest(option,(response)=>{
   let user.ids = response.data.ids
   AsyncRequest(options,(response) => {
      let vlaue = response.data.posts
   })
})

برای مدیریت این پردازش ها اگر از Callback استفاده کنیم با مشکلات زیر مواجه خواهیم شد:

  • syntax بسیار گیچ کننده ای دارد و دائما این Callback ها به لایه های داخلی درخواست ها متصل می شوند.
  • مدیریت حالت خطا در هر یک از این درخواست های Asynchronous ناممکن و سخت است . یعنی باید به ازای هر حالت یک مدیریت خطا داشته باشیم.

برای جلوگیری از این حالت بهتر است از Promise استفاده کنیم که موارد بالا را به صورت کامل پشتیبانی می کند و همچنین با در اختیار گذاشتن یک سری Static/Dynamic متد یک syntax بسیار خوب را رقم میزند.

Promise Object

Promise یک شئ است که می تواند مقدار خروجی یک پردازش را (مثلا یک تابع) در خود می پیچد (Proxy Design Pattern) و این امکان را برای ما فراهم می کند که به ازای صحت یا عدم صحت یک پردازش یک سری Callback را به صحیح ترین شکل syntax متصل کنیم.

پس Promise راه حلی است برای مدیریت پردازش های ناهمگام که از جریان اصلی برنامه جدا می گردند. زمانی که یک function یا procedure می نویسید که خروجی آن یک Promise است ایجاد کننده آن Promise هستید و زمانی که از این تابع (یا توابع پیش فرض جاوا اسکریپت مثل fetch) استفاده کننده از Promise هستید.

ما بیشتر از Promise استفاده می کنیم یعنی توابعی که به قرار است به صورت Asynchronous اجرا گردند یک خروجی Promise برای ما بر می گردانند و ما با استفاده از آن می توانیم مراحل بعدی این پردازش را به ازای صحت و یا عدم صحت پردازش قبل انجام بدهیم.

یک شئ Promise نشان دهنده صحت یا عدم صحت یک پردازش اصلی است که در Promise Constructor Execution قرار گرفته است می باشد. این صحت یا عدم صحت به وسیله دو تابع resolve, reject قابل تعیین می باشد که اجرای هر کدام از آن ها تعیین تکلیف Promise است و وضعیت یک Promise را تعیین می کند.

برای استفاده از Promise باید یک شئ از کلاس آن بسازید که در متد سازنده آن باید یک تابع اجرا کنید که دو پارامتر می گیرد :

  • resolve : در صورت موفقیت آمیز بودن عملیات آن را باید اجرا کنید. صحت اجرای این عملکرد به ازای یک مقدار ( Resolve By value)
  • reject : در صورت عدم موفقیت باید آن را اجرا کنید. عدم صحت اجرای عملکرد به ازای یک دلیل (Reject By Reason)
var promise = new Promise(function(resolve, reject) {
   //Process Body
   // Resolve, Reject 
});

هر Promise سه وضعیت دارد که بسته به اجرای هر یک از توابع Resolve , Reject این وضعیت ها تعیین می گردد و هر Promise می توانید تنها یکی از این وضعیت ها را داشته باشد یعنی این که اگر resolve را اجرا کردید به وضعیت fullfilled می رود اجرای Reject تاثیری ندارد:

  • حالت اول pending می باشد که هنوز نتیجه پردازش مشخص نشده است (حالت انتظار) .
  • حالت دوم fulfilled که در صورت موفقیت آمیز بودن پردازش اتفاق می افتد
  • حالت سوم rejected که در صورت عدم موفقیت اتفاق می افتد.

در مثال زیر این سه حالت را بررسی می کنیم:

const A = new Promise( (res,rej) => { console.log('promise pending') } )
const B = new Promise( (res,rej) => { res(true) })
const C = new Promise( (res,rej) => { rej('error') })

console.log(A,B,C)
//Promise { <pending> } Promise { true } Promise { <rejected> 'error' }

یک Promise نمی تواند دو وضعیت داشته باشد. پس به ازای اجرای هر یک از موارد resolve, reject اگر مورد بعدی اجرا شود بی تاثیر است. این دو تابع ورودی های Promise Constructor Callback می باشد.

Promise Instance(Dynamic) Methods

متد های داینامیک یک Promise که به آن ها Promise Handler نیز می گوییم می تواند زنجیره مدیریت بدنه پردازش ناهمگام که می تواند خروجی یک تابع باشد را برای ما فراهم کند.

Promise.prototype.then برای مدیریت حالت موفقیت آمیز بودن یک Promise می باشد که حاصل اجرای تابع resolve در Promise Constructor Execution می باشد. این متد هم حالت موفقیت و هم خطا را مدیریت می کند. (fulfillment/rejection handler)

samplePromise.then((resolveValue)=>{
     console.log(resolveValue);
}, (rejectReason) => {
     console.error(rejectReason);
})

معمولا then را برای حالت صحت عملکرد بدنه پردازش ناهمگام استفاده می کنند و برای عدم صحت از reject استفاده می کنند.

Promise.prototype.catch این متد برای مدیریت وضعیت خطای بدنه پردازش می باشد . یعنی همان زمانی که در بدنه پردازش مقدار reject را به ازای یک reason فراخوانی کردیم.(rejection handler)

samplePromise.then((value)=>{
     console.log(value);
}).catch((reason)=> {
     console.error(reason);
})
Promise.Prototype.finally این متد فارغ از صحت یا عدم صحت پردازش اصلی در انتهای هر Promise Chain یک بار برای همیشه اجرا می گردد.(append to handlers)

samplePromise.then((value)=>{
     console.log(value);
}).catch((reason)=> {
     console.error(reason);
}).finally(() => {
     console.log("finally");
})

setTimeout یک تابع Asynchronous در جاوا اسکریپت می باشد که با استفاده از آن می توانیم بک پردازش را پس از گذراندن یک زمان به میلی ثانیه اجرا کنیم. برای استفاده از مثال Promise از این تابع استفاده می کنیم:

let asyncProcess = () => {
     return new Promise((resolve,reject)=>{
          setTimeout(() => {
               resolve('successfully')
          }, 2000);
     });
}
/* Promise Usage */
asyncProcess.then((resolveValue)=>{
     console.log(resolveValue);
})

تصویر بالا جریان اجرایی یک پردازش ناهمگام را نمایش می دهد. در ابتدای امر یک پردازش ناهمگام مثل setTimeout یا Ajax Request اجرا می گردد که وضعیت Promise فعلا تا مشخص نشدن نتیجه Pending است . پس از اتمام نتیجه پردازش به ازای یکی از حالت های صحت یا عدم صحت بدنه پردازش در Promise Constructor Execution وضعیت Promise به یکی از موارد fulfill یا reject در می آید که حالا نوبت مدیریت هر یک از این وضعیت ها هستیم . به ازای صحت می توانیم متد then را با مقدار حاصل از پردازش فراخوانی کنیم و به ازای عدم صحت می توانیم متد catch را به ازای دلیل عدم صحت که از بدنه پردازش برگشته فراخوانی کنیم.

در نهایت می توانیم این پردازش را تا رسیدن به finally Promise Handler دنبال کنیم و یک زنجیره ای از پردازش های ناهمگام را فراخوانی گردانیم که به آن زنجیره مدیریت پردازش ناهمگام یا Chained Promise Handlers می گوییم که از مهم ترین ویژگی های Promise می باشد.

مطالب مشابه

Array in JavaScript

ساختمان داده آرایه در  جاوا اسکریپت به نوع داده ای که یک سری مقدار با نوع داده های متفاوت یا یکسان پشت سر هم در قالب یک...

Javascript Symbol

در جاوا اسکریپت ما دو دسته داده داریم . دسته اول Primitive است و دسته دوم non-primitive است. داده های از نوع Primitive...

OOP Paradigm fundamental

شئ گرایی یک الگوی تفکر طراحی سیستم های نرم افزاری است که این تفکر باعث نگاشت موجودیت های سیستم (Enitity) یا عملیات ها...

اشتراک گذاری :

مدیر وب سایت گنوتک . برنامه نویسی رو با زبان C در هفده سالگی شروع کردم . در حال حاضر به برنامه نویسی php برپایه معماری MVC , HMVC و همچنین سیستم مدیریت محتوای WordPress و فریم ورک محبوب لاراول علاقه مند هستم و دوست دارم اطلاعاتم رو با شما به اشتراک بگذارم.

۰ دیدگاه برای مدیریت پردازش های ناهمگام با Promise – قسمت یک

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *