آموزش ساخت صف یا Queue با پشتیبانی از Async در جاوا اسکریپت

مثال صف یا queue در جاوا اسکریپت

در جاوا اسکریپت یک نخ اصلی یا همان Main thread وجود دارد که به صورت پیش‌فرض همه کدها در آن اجرا می‌شود. انجام عملیات ناهمگام یا Asynchronous مثل ارسال یک درخواست HTTP توسط خود مرورگر یا محیط nodejs در نخ و thread جداگانه انجام شده و نتیجه اش به ما در Main thread بازگردانده می‌شود.

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

کد زیر نمونه پیاده سازی یک صف و queue در جاوا اسکریپت است که از توابع async و promise پشتیبانی می‌کند و خطا ها را نیز هندل می‌کند:

const createQueue = () => {
  const queue = {
    list: [],
    isProcessing: false,
    async process() {
      if (!queue.list.length) {
        queue.isProcessing = false;
        return;
      }
      const oper = queue.list.shift();
      if (oper) {
        await oper();
      }
      queue.process();
    },
    add(oper) {
      let resolve;
      let reject;
      const promise = new Promise((rs, rj) => {
        resolve = rs;
        reject = rj;
      });
      const wrappedOper = async () => {
        try {
          resolve(await oper());
        } catch (error) {
          reject(error);
        }
      };

      queue.list.push(wrappedOper);
      if (!queue.isProcessing) {
        queue.isProcessing = true;
        queue.process();
      }

      return promise;
    },
  };
  return queue;
};

نحوه استفاده به این صورت است که ابتدا یک صف با createQueue() ایجاد می‌کنید سپس با متد add می‌توانید توابع async خودتان را در صف قرار دهید. متد add خودش یک promise به شما می‌دهد که پس رسیدن نوبت و اجرای تابع شما resolve می‌شود و نتیجه آن همان خروجی (یا خطا) است که تابع شما برمی‌گرداند.

const myQueue = createQueue();

console.time('first');
console.time('second');
console.time('third');
console.time('no queue');

myQueue.add(() => new Promise(r => setTimeout(r, 3000)))
  .then(() => console.timeEnd('first'));

myQueue.add(() => new Promise(r => setTimeout(r, 1000)))
  .then(() => console.timeEnd('second'));

myQueue.add(() => new Promise(r => setTimeout(r, 2000)))
  .then(() => console.timeEnd('third'));

console.timeEnd('no queue');

تجزیه و تحلیل کد

در اینجا کد اصلی مربوط به صف داخل یک تابع قرار دارد تا این امکان را داشته باشیم که هر تعداد صف مجزا از هم خواستیم، ایجاد کنیم:

const createQueue = () => {
  const queue = {
    ...
  };
  return queue;
};

// این صف ها جدا از هم هستند
const queue1 = createQueue();
const queue2 = createQueue();
const queue3 = createQueue();

خود آبجکت queue دارای دو ویژگی list و isProcessing و دو متد process و add است:

const queue = {
  list: ...,
  isProcessing: ...,
  process() { ... },

  add(oper) { ... },
};
  • ویژگی list یک آرایه برای نگهداری توابعی است که به صف اضافه شده اند.

  • ویژگی isProcessing وضعیت صف را نشان می‌دهد که درحال پردازش است یا نه.

  • متد process برای شروع یا خاتمه پردازش لیست به صورت بازگشتی اجرا می‌شود.

  • متد add برای افزودن تابع به صف استفاده می‌شود. (پس از ساخت صف، تنها از این استفاده می‌کنیم.)

عملکرد و نحوه کار این کد

یک صف FIFO

ترتیب صف به صورت FIFO است یعنی اولین تابعی که اضافه شده اولین تابعی است که انجام شده و خاتمه می‌یابد. قدم به قدم هر مرحله را بررسی می‌کنیم:

  1. پس از ایجاد یک صف، با فراخوانی متد add یک تابع به عنوان ورودی (oper) جهت اضافه شدن به صف می‌دهیم.

  2. داخل متد add یک promise ساخته شده و خروجی یا خطای تابع اصلی ما یعنی oper از طریق یک تابع احاطه کننده یعنی wrappedOper به آن promise وصل می‌شود.

  3. سپس تابع wrappedOper به انتهای آرایه list اضافه می‌شود (با list.push) و اگر لیست درحال پردازش و اجرا نبود (کنترل isProcessing) متد process جهت شروع پردازش، فراخوانی می‌شود.

  4. در خروجی متد add یک promise برگردانده شده که پس از اجرای wrappedOper و در نتیجه اجرای oper (همان تابع اصلی ما) نتیجه‌اش در این promise منعکس می‌شود.

  5. متد process که داخل متد add (اگر صف درحال پردازش نبود) اجرا می‌شود، اولین تابع را از آرایه list برداشته (با list.shift) و اجرا می‌کند.

  6. متد process به صورت بازگشتی تا زمانی که هیج آیتمی در آرایه list باقی نمانده باشد، خودش را فراخوانی می‌کند و مقدار isProcessing را طبق آخرین وضعیت list بروز می‌کند.


یادگیری کامل جاوا اسکریپت را می‌توانید از اینجا شروع کنید 👈


کامنت ها