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

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

دانستیم که یکی از بهترین راه های مدیریت پردازش های ناهمگام که خارج از جریان اصلی برنامه اجرا می شوند استفاده از Promise به جای برگرداندن مقدار مستقیم است ( که از اصل Proxy Design Pattern ) پیروی می کند. روش هایی که ما یک Promise بر می گردانیم از قرار زیر است :

  • new Promise ، این که مستقیما یک Promise را بسازیم .
  • در تابع خود مقدار خروجی را در Promise برگردانیم که روش رایج تری است .
  • استفاده از کلمه کلیدی async در تعریف تابع.

مدیریت پردازش های ناهمگام به صورت Sequential , Parallel با استفاده از Instance/Static Promise Methods

معمولا یک تابع ناهمگام به دلیل جریان داخلی که در بدنه تابع وجود دارد به صورت ناهمگام یا Asynchronous تعریف می شود. به این صورت که مطمئن جریان ناهمگام دیگری در بدنه تابع وجود دارد. به طور مثال زمانی که یک تابع می نویسیم که لیست کاربران را از سرور دریافت کند در آن یک تابع دیگر وجود دارد که درخواست دریافت لیست کاربران را به سرور می دهد( به طور مثال تابع fetch ).

Asynchronous/Synchronous Functions

Asynchronous Synchronous
در این نوع توابع اجرا خارج از جریان برنامه در Microtask ها اتفاق می افتد و ما نمی توانیم صحت یا عدم صحت آن را در خطوط برنامه پیش بینی کنیم. در این نوع توابع اجرا خط به خط اتفاق می افتد و صحت و یا عدم صحت اجرای پاسخ این تابع در خطی که فراخوانی شده است معلوم است.
خروجی این دسته از توابع قابل در همان خطی که اجرا شده است قابل تعیین نمی باشد و نمی توان آن را به صورت عادی در متغیر قرار بدهیم. خروجی این دسته از توابع در همان خطی که اجرا شده است قابل تعیین و مقدار گذاری در متغیر خاص می باشد.
function foo(){
    return new Promise((res,rej) => {
        setTimeout(() => {
            res(2)
        })
    })
}
let val = foo()
console.log(val) //Promise { <state>: "pending" }

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

async keywords

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

کلمات کلیدی async/await در راستای هم برای مدیریت صحیح تر توابع ناهمگام مبتنی بر Promise با یک syntax بهتر و تمیز تر و بدون نیاز به ایجاد یک سری زنجیره then می باشند.کاربرد این دو کلمه کلیدی در روش اجرای پردازش ها به صورت Sequential یا همان relatively می باشد.

async function name(param0) {
  statements
}

Params

تعیین پارامتر ها برای توابع ناهمگامی که با کلمه کلیدی async تعریف شده اند دقیقا مطابق با همان توابع عادی است .

Statement Body

مطمئنا زمانی که یک تابع را به صورت ناهمگام با کلمه کلیدی async تعریف می کنیم روال به این شکل است که در بدنه این تابع پردازش های ناهمگام دیگری نیز تعریف شده باشد.برای بدست آوردن مقدار مستقیم یک پردازش ناهمگام در یک تابع ناهمگام که با استفاده از کلمه کلیدی async تعریف شده است می توان از مکانیزم await استفاده کرد.

Return Value

یک مقدار Promise.resolve یا یک Error که پرتاب شده و باید در بلوک try…catch آن را قرار بدهیم. به این ترتیب که مدیریت خطا در این گونه توابع با استفاده از بلوک try…catch می باشد.

async function foo() {
  return 1;
}

دقیقا برابر است با :

function foo() {
  return Promise.resolve(1);
}

await keyword

کلمه کلیدی await در بدنه یک تابع async قرار می گیرد و به صورت مستقل کاربردی ندارد و اگر به صورت مستقل استفاده کنیم خطا Syntax Error با متن زیر دریافت خواهیم کرد.

Uncaught SyntaxError: await is only valid in async functions, async generators and modules

کلمه کلیدی await باعث می شود که پردازش ناهمگام در بدنه تابع async به صورت synchronous اجرا گردد و تابع را در همان نقطه تا زمان برنگشتن مقدار آن پردازش نگه دارد و بتوان بدون استفاده از بلوک های then…catch مقدار را با یک syntax خیلی تمیز تر بدست آورد.

استفاده از کلمات کلیدی async/await برای ساده سازی syntax استفاده از Promise می باشد (consume Promise Based Api) استفاده از async/await برای مدیریت پردازش های ناهمگام بسیار شبیه به استفاده از Promise و Generators می باشد.

در مثال زیر نحوه اجرای یک Promise را در بدنه یک تابع async با استفاده از کلمه کلیدی await مشاهده می فرمایید .

async function foo(){
  const result = await new Promise ( (res,rej) => {
    setTimeout( () => res(1) )
  })
  console.log(result)

  const result2 = await new Promise ( (res,rej) => {
    setTimeout( () => res(2) )
  })
  console.log(result2)
}

console.log('script start')
foo()
console.log('script end')

/*script start 
script end
1
2*/

همان طور که از روند اجرا پیداست این تابع یک تابع ناهمگام است که خارج از جریان timing برنامه اجرا می شود .این تابع در بدنه خود دو پردازش Asynchronous دارد که مقادیر 1, 2 را قرار است برگردانند. پس می توان بدنه این تابع را به ۲ قسمت (بر اساس await) تقسیم بندی کرد. هر قطعه کدی که بعد از await اجرا می شود دقیقا حکم یک then callback را دارد .

این پردازش ها به صورت ناهمگام در بدنه این تابع اجرا می گردند. نحوه اجرای این تابع به این شکل است که تابع تا رسیدن به اولین await در بدنه تابع اجرا می شود و در این نقطه تا پایان این پردازش اجرای تابع را متوقف می کند و به بدنه اصلی برنامه رفته و سایر statement ها را اجرا می کند. پس از اتمام اولین await به تابع بر می گردد و به await بعدی می رود و تا پایان رسیدن نتیجه این پردازش نیز تابع را freez می کند و در نهایت مقدار آن را می گیرد.

Error Handling in async/await

برای مدیریت خطا در این گونه توابع لازم است که آن دسته از پردازش هایی که قرار است به صورت ناهمگام اجرا شوند و با استفاده از کلمه کلیدی await آن ها را تعریف کنیم در بلوک try…catch بگذاریم :

async function foo(){
    try{
    const result = await new Promise ( (res,rej) => {
      setTimeout( () => rej('error occured') )
    })
    console.log(result)
    const result2 = await new Promise ( (res,rej) => {
      setTimeout( () => res(2) )
    })
    console.log(result2)
    }catch(e){
        console.log(e)
    }
}

console.log('script start')
foo()
console.log('script end')
/*script start 
script end
error occured*/

در صورتی که خطای موجود در بدنه این تابع را دریافت نکنیم خطای Uncaught (in promise) خواهیم داشت

یک مثال کاربردی تر از دریافت پست های کاربران با استفاده از async/await را در زیر مشاهده می کنید.

async function loadPosts(){
  let username = 'admin'
  let userResponse = await fetch(`users.php?username=${username}`)
  let userResponseData = await userResponse.json()
  let userId = userResponseData.data.user.id
  let postsResponse = await fetch(`posts.php?user_id=${userId}`)
  let postsResponseData = await postsResponse.json()
  let posts = postsResponseData.data.posts
  return posts
}

این مثال به صورت ساده و با استفاده از then handlers در بخش دوم مدیریت پردازش های ناهمگام بررسی شده است . همان طور که می بینید با این روش یک syntax کم تر و بهتر خواهیم داشت.

Example

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

export default function getImageList(){
  return new Promise( (resolve,reject) => {
    let request = new XMLHttpRequest
    request.responseType = 'json'
    request.open('GET','glob.php')
    request.send()
    request.onload = () => {
      if(4 == request.readyState && 200 == request.status){
        resolve(request.response)
      }
      reject('error occured with fetching image list')
    }
  })
}

در ماژول بالا تنها یک درخواست به یک وب سرویس می دهیم تا لیست تصاویر را برای ما برگرداند. این وب سرویس یک json  بر می گرداند که شامل لیست تصاویر موجود در دایرکتوری images است.

export default function loadImageBlob(relativePath) {
  return new Promise( (resolve,reject) => {
    let request = new XMLHttpRequest
    request.responseType = 'blob'
    request.open('GET',relativePath)
    request.send()
    request.onload = () => {
      if(4 == request.readyState && 200 == request.status){
        resolve(request.response)
      }
      let statusText = request.statusText
      reject(`error with image load ${statusText}`)
    }
  })
}

ماژول بالا یک تصویر را دریافت می کند و از روی مسیر آن و در نهایت blob آن را بر می گرداند می توان یک لینک برای آن ساخت و آن را در یک dom قرار داد. در این مثال ما از request.onload استفاده کردیم به این علت که لازم است تنها زمانی callback را اجرا کنیم که درخواست به پایان خود رسیده باشد. نکته بعدی این است که request.responseType در آن برابر با blob است . این درخواست به این شکل است که ما درخواست عادی برای دریافت یک فایل می دهیم. در نهایت خروجی این درخواست به صورت یک Promise است ، چون این درخواست قرار است به صورت ناهمگام اجرا گردد و ما بتوانیم sequence پردازش های بعدی را اجرا کنیم.

import loadImageBlob from './image-load.js'
import getImageList from './image-list.js'


getImageList()
  .then( (response) => {
  return response.data.images
}).then( (imageList) => {
  let imageReqs = []
  imageList.forEach( (item,index) => {
    imageReqs.push(loadImageBlob(item))
  }) 
  return Promise.all(imageReqs)
}).then( (imageBlobs) => {
  imageBlobs.forEach( (blob,index) => {
    let image = new Image()
    let imageUrl = window.URL.createObjectURL(blob)
    image.src = imageUrl
    document.querySelector('.gallery').append(image)
  })
}).catch( (reason) => {
  console.error(reason)
})

در پردازش بالا ما از دو ماژول گفته شده در اجرای نهایی پردازش استفاده می کنیم . در گام اول ما لیست تصاویر را دریافت می کنیم و در گام دوم مسیر های تصاویر را به صورت یک آرایه imageReqs.push(loadImageBlob(item)) از نوع Promise در می آوریم و پس از آن به وسیله Promise.all این لیست را تبدیل به یک سری لینک کرده و در dom به صورت یک گالری قرار می دهیم.

مطالب مشابه

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

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

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

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

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