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

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

بزرگترین کاربرد Promise استفاده از آن ها در توابع Asynchronous یا ناهمگام است برای برطرف کردن بزرگترین مشکل آن ها یعنی اتصال Callback ها به آن ها در بهترین شکل Syntax و همچنین مدیریت خطا به صورت یکپارچه در آن ها می باشد. این مهم به اشکال زیر مورد استفاده قرار می گیرد:

  • نوشتن یک تابع با عملکرد Asynchronous که یک Promise را بر می گرداند.
  • استفاده از یک تابع پیش فرض یا همان تابعی که خودمان نوشتیم با عملکرد Asynchronous یا غیر همگام که یک Promise بر می گرداند و اتصال یک سری Callback به منظور هدفی خاص که سرانجام این پردازش های ناهمگام است.(بیشتر ما استفاده کننده از Promise هستیم)

توابع ناهمگام و نحوه مدیریت آن ها توسط Promise و Callback را بررسی بفرمایید.

Promise Sequential Chain

جریان برنامه در پردازش های همگام یا Synchronous به این شکل است که پردازش ها خط به خط اجرا می شوند و ما می توانیم تعیین کنیم که کدام پردازش پس از کدام پزدازش می باشد. در پردازش های ناهمگام Asynchronous این موضوع صدق نمی کند و هر پزدازش بنا به زمان اجرای خود ممکن است زود تر یا دیر تر از پردازش قبل اجرا گردد. این موضوع زمانی سخت تر می شود که قرار باشد تعداد زیادی پردازش ناهمگام را به صورت Sequential یا Concurrent (پشت سر هم یا همزمان) اجرا گردند.

پاراگراف قبل مقدمه ای بر لزوم اتصالات در پردازش های ناهمگام با استفاده از Promise Handlers می باشد. این مثال را در نظر بگیرید که قرار است یک سری کاربر را از سرور دریافت کنیم و پس از آن یک سری پست از کاربران دریافتی از سرور دریافت کنیم. در این حالت مقدار اولیه پردازش اول به پردازش دوم می رود.

const userPromise = getUsers()
const postPromise = userPromise.then( (users) => { getPosts(users) })

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

const foo = new Promise ( (resolve,reject) => {
   resolve('foo')
})

const bar = new Promise ( (resolve,reject) => {
   resolve('bar')
})

foo.then( (result) => {
   console.log(result)
   return bar
}).then( (result2) => {
   console.log(result2)
}).catch( (error) => {
   console.error(error)
})

یک مثال ساده از اتصال Callback ها به پردازش اصلی را در بالا مشاهده می کنید. در این نوع اتصالات پردازش ها :

  • در این شکل از اتصالات هر then خود یک Promise را بر می گرداند .
  • هر گام یا حلقه از این زنجیره پس از اتمام پردازش گام قبل خود می باشد.
  • نتیجه هر اتصال از گام قبل خود دریافت می شود . پس باید اگر قرار است این اتصال ادامه پیدا کند باید در then constructor callback مقدار را برگردانیم.
  • هر زمان هر خطایی در این گام های اتصالات رخ بدهد به catch منتهی می شویم.

Promise chain callback

در تصویر بالا ما اولین پردازش ناهمگام را در Promise Constructor Callback اجرا کرده ایم و نتیجه آن یک Promise است که صحت (fulfillment) یا عدم صحت (reject) می باشد . در گام دوم از این اتصالات ما دقیقا همین روال را در then Constructor Callback اجرا می کنیم. این مورد هم یک Promise بر می گرداند که باید با Promise Handlers مدیریت شود. این مراحل به صورت گام به گام پشت سر هم می تواند تا چندین مورد Promise اتفاق بیفتد.

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

fetch('users.php?username=admin').then( (response) => {
  return (response.ok)? return response.json() : throw new Error('user not found!!')
}).then( (collection) => {
  return collection.data.user.id
}).then( (userId) => {
  return fetch(`posts.php?user_id=${userId}`)
}).then( (response) => {
  return (response.ok)? return response.json() : throw new Error('posts not found')
}).then( (responseData) => {
  posts = responseData.data.posts
  console.log(posts)
}).catch( (error) => {
  console.error(error)
})

 

اشتباهات رایج ۱ : دقت داشته باشید که باید به ازای هر زنجیره از اتصالات یک catch داشته باشیم و اگر بیش از یک catch برای این زنجیره مجبور شدیم تعریف کنیم مطمئنا در روند پیاده سازی اشتباه داشته ایم. اگر در مثال بالا به جای برگرداندن مقدار fetch در آن مستقیم then را فراخوانی کنیم نیاز است که در داخل then این خطا را مدیریت کنیم .

اشتباهات رایج ۲ : دقت داشته باشید این اتصالات باید در یک Indent خاص باشند اگر برای راه اندازی اتصالات درگیر اجرای then handler های تو در تو شدیم مطمئنا در روند پیاده سازی اشتباهاتی داشته ایم.

Promise Composition Parallel

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

  1. Respectively : به ترتیب است . یعنی قرار است این پردازش ها به صورت حلقه های یک زنجیر یکی پس از دیگری اجرا شوند.
  2. Parallel : به صورت موازی اجرا گردند. یعنی در یک بلوک همگی با هم اجرا گردند و صحت و عدم صحت آن ها در یک زمان تعیین شود.

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

Promise Static Methods

متدهای ایستا Promise که مربوط به عملکرد موازی در اجرای پردازش های ناهمگام دارند (نام دیگر آن ها نیز composition tool است) به شرح زیر می باشند:

Promise.all(iterable) انتظار برای صحت همه پردازش های ناهمگام پاس داده شده در قالب آرایه و یا اولین عدم صحت یکی از پردازش های موجود در آرایه .(بعد از برخورد به اولین عدم صحت پردازش یا اتمام آخرین صحت پاسخ پردازش)

async function usersStepOne() {
  return await fetch('https://reqres.in/api/users?page=1')
}

async function usersStepTow(){
  return await fetch('https://reqres.in/api/users?page=2')
}

Promise.all([
  usersStepOne(),
  usersStepTow(),
]).then(([responsePageOne,responsePageTow]) => {
  /** instead of return [resonsePageOne.json(),responsePageTow.json()] **/
  Promise.all([
    responsePageOne.json(),
    responsePageTow.json(),
  ]).then( ([usersPageOne,usersPageTow]) => {
    let users = {...usersPageOne.data,...usersPageTow.data}
    console.log(users)
  })
})

اشتباه رایجی که در استفاده از Promise ها می شود این است زمانی که قرار است در طول برنامه جواب دو پردازش غیر همگام را در یک آرایه داشته باشید هر دو را در آرایه قرار می دهند برای اجرا که باید از متد های ایستا استفاده شود . در مثال بالا این اشتباه رایج را در قالب کامنت قرار داده ایم.

Promise.allSettled(iterable) انتظار برای این که تمامی پردازش های موجود در آرایه پارامتر ورودی به پایان برسد ( حال صحت یا عدم صحت آن ها ) . خروجی این متد یک شئ است که دو ویژگی دارد . ویژگی اول status است که یکی از موارد زیر است :

  • fulfill : به معنای صحت عملکرد
  • reject : عدم صحت پاسخ این پردازش
Promise.allSettled([
  new Promise( (resolve,reject) => {
    resolve(10)
  }),	
  new Promise( (resolve,reject) => {
    setTimeout( () => {
      resolve(54)
    },2000)
  }),
]).then( (values) => {
  console.log(values)
} )
Promise.any(iterable) انتظار برای رسیدن به اولین صحت پاسخ پردازش موجود در لیست پردازش های موجود در آرایه پارامتر های ورودی

const A = new Promise( (res,rej) => {
   setTimeout(rej,3000,'A')
})

const B = new Promise( (res,rej) => {
   setTimeout(res,5000,'B')
})

const C = new Promise( (res,rej) => {
   setTimeout(res,4000,'C')
})

Promise.any([A,B,C]).then( (result) => {
   console.log(result)
})
Promise.race(iterable) انتظار برای رسیدن به صحت یا عدم صحت پاسخ یکی از پردازش های موجود در آرایه پارامتر های ورودی (این کاملا به معنی پایان یکی از پردازش های موجود است)

const A = new Promise( (res,rej) => {
    setTimeout(rej,3000,'A')
})

const B = new Promise( (res,rej) => {
    setTimeout(res,5000,'B')
})

const C = new Promise( (res,rej) => {
    setTimeout(res,4000,'C')
})

Promise.race([A,B,C]).then( (result) => {
    console.log(result)
}).catch( (reason) => {
 	console.error(reason) 
})

//warning: Promise.any is not defined in node js, run this code in mozilla imulator

در میان متد های بالا پرکاربرد ترین آن ها Promise.all و Promise.allSettled می باشد. تفاوت های این دو متد به شرح زیر است :

  • در Promise.all پردازش های ناهمگام موجود در این پارامتر ورودی با هم مرتبط هستند و صحت و عدم صحت پاسخ اجرای هر یک به سایرین مربوط می باشد ولی در Promise.allSettled عکس این مورد است.
  • در Promise.all پاسخ برگشت داده شده از اولین اجرا توسط این متد یک سری مقادیر است که بسته به نوع پاسخ می تواند داده خام یا Promise باشند ولی در Promise.allSettled شئ ایست که در بالا توضیح داده شده است.
  • در Promise.all اگر حتی یکی از پردازش ها به خطا بخورد یعنی عدم صحت پاسخ آن را داشته باشیم مقدار نتیجه برابر است با Error و به Catch منتهی می شود اما در Promise.allSettled تنها پایان تمامی پردازش ها مهم است که نتیجه را طی گزارشی درقالب شئ بر میگرداند (صحت یا عدم صحت عملکرد هر یک در پایان نتیجه تاثیری ندارد)

اشتراکات این دو متد ایستا این است که هر دو با همان ترتیبی که در ورودی پردازش های همگام را در قالب یک آرایه برگرداند با همان ترتیب پس می دهند.

مطالب مشابه

Obeject Data Structure in JavaScript

هر زبان برنامه نویسی از دو ساختار اصلی تشکیل شده است . بخش اول (Data Structure) ساختمان های داده آن و بخش دوم لیست...

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

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

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

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

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