جاوا اسکریپت Async: تکمیل کتابخانه AJAX + معرفی promise ها

جاوا اسکریپت Async: تکمیل کتابخانه AJAX + معرفی promise ها

تکمیل کتابخانه AJAX

در قسمت قبل توانستیم کدهای متد POST را به طور کامل بنویسیم، حالا زمان تکمیل کدهای کتابخانه و کدنویسی متد های Put و Delete است. برای کد نویسی Put کار بسیار ساده ای را در پیش داریم چرا که هر put یا همان update یک نوع POST به شمار می رود. چرا؟ به دلیل اینکه در درخواست های put هنوز هم داده های جدید را به سمت سرور ارسال می کنیم تا آنجا ثبت شوند، دقیقا کاری که در POST می کردیم! بنابراین:

// Make an HTTP PUT Request
easyHTTP.prototype.put = function(url, data, callback) {
  this.http.open('PUT', url, true);
  this.http.setRequestHeader('Content-type', 'application/json');

  let self = this;
  this.http.onload = function() {
    callback(null, self.http.responseText);
  }

  this.http.send(JSON.stringify(data));
}

توضیح کد:

  • درخواست های put ما هم سه پارامتر می گیرند: پارامتر اول url است، پارامتر دوم data و پارامتر سوم callback می باشد. مثل همیشه url برابر با end-point یا نقطه ی دسترسی روی سرور است، data اطلاعات جدیدی است که می خواهیم جایگزین اطلاعات قبلی شود (درخواست put نوعی update است) و callback نیز تابعی برای async کردن کد و جلوگیری از خطاهای مربوط به اختلاف اجرای پاسخ سرور و کدهای ما است.
  • روند کاری کدها دقیقا مثل جلسه ی قبل است: بخش open مسئول تعیین نوع متد درخواست است که برای ما می شود PUT. بخش تعیین header نیز باید روی 'application/json تعیین شده باشد چرا که اطلاعات رد و بدل شده بین برنامه ی ما و سرور JSONPlaceholder از نوع JSON است.
  • استفاده از متغیر self برای حذف مشکل this در جاوا اسکریپت است که در دو جلسه ی قبل توضیح داده شد.
  • تابع onload تعیین می کند که در هنگام دریافت پاسخ چه کاری انجام شود و ما طبق معمول آن را log خواهیم کرد.
  • در نهایت برای ارسال درخواست، اطلاعات را نیز به عنوان پارامتر JSON شده به آن داده ایم.

برای تست این کد به فایل app.js بروید و تمام کدهای قبلی را کامنت کنید، البته شیء data را نگه دارید چرا که از آن به عنوان داده های ارسالی PUT استفاده خواهیم کرد. سپس کد زیر را بنویسید:

// Update Post
http.put('https://jsonplaceholder.typicode.com/posts/5', data, function(err, post) {
  if(err) {
    console.log(err);
  } else {
    console.log(post);
  }
});

همانطور که می بینید هیچ تفاوتی بین این کد و کدهای جلسه ی قبل وجود ندارد، به جز اینکه url ارسال شده را با id پنج ارسال کرده ایم. در سرور تمرینی JSONPlaceholder این id مشخص می کند که چه resource ای (چه اطلاعاتی، چه پستی و...) ویرایش شود و نتیجه نیز به شکل زیر است که به ما می گوید درخواست به صورت صحیح ارسال شده است:

پاسخ سرور به درخواست PUT
پاسخ سرور به درخواست PUT

برای نوشتن کدهای Delete نیز به سادگی می گوییم:

// Make an HTTP DELETE Request
easyHTTP.prototype.delete = function(url, callback) {
  this.http.open('DELETE', url, true);

  let self = this;
  this.http.onload = function() {
    if(self.http.status === 200) {
      callback(null, 'Post Deleted');
    } else {
      callback('Error: ' + self.http.status);
    }
  }

  this.http.send();
}

کد بالا شباهت زیادی به کد GET دارد. احتمالا از خودتان بپرسید چرا برای Callback مقدار responseText را قرار نداده ام. پاسخ شما این است که در متد Delete چیزی توسط سرور JSONPlaceholder به ما برگردانده نمی شود به جز یک شیء خالی که نشان از حذف شدن اطلاعات دارد بنابراین من رشته ی Post Deleted را به صورت دستی اضافه کرده ام تا بدانیم که درخواست به شکل صحیح ارسال شده است.

مثل همیشه برای تست کدها به app.js می رویم و می گوییم:

// Delete Post
http.delete('https://jsonplaceholder.typicode.com/posts/1', function(err, response) {
  if(err) {
    console.log(err);
  } else {
    console.log(response);
  }
});

مثل درخواست PUT برای درخواست های DELETE نیز باید مشخص کنیم که هدف ما چیست، چه چیزی را می خواهیم حذف کنیم. همچنین نام پارامتری که به تابع callback می دهید هیچ اهمیتی ندارد، من در مثال بالا آن را response گذاشته ام اما هر نام دیگری نیز که باشد باعث مشکل نخواهد شد. پاسخ سرور را نیز در تصویر زیر می بینید:

پاسخ سرور به درخواست DELETE
پاسخ سرور به درخواست DELETE

امیدوارم از تکمیل این کتابخانه لذت برده باشید.

آشنایی با Promise ها در ES6

همانطور که قبلا گفته بودم پس از اتمام کدنویسی این کتابخانه آن را به نسخه ی ES6 تغییر می دهیم بنابراین باید به این قول عمل کنم اما قبل از شروع چنین مبحثی باید شما را با برخی از مباحث پایه آشنا کنم. تا اینجای کار ما تنها با شیء قدیمی XMLHttpRequest کار کرده ایم اما برای نسخه ی ES6 می خواهیم از Promise ها و Fetch API و روش های جدیدتر کمک بگیریم بنابراین بهتر است نگاهی به promise ها داشته باشیم.

Promise ها در واقع جایگزین callback ها و نسخه ی جدیدتر آن ها هستند بنابراین از همان مثالی که در جلسه ی callback ها داشتیم (چند جلسه ی قبل) استفاده می کنم:

const posts = [
  {title: 'Post One', body: 'This is post one'},
  {title: 'Post Two', body: 'This is post two'}
];

function createPost(post, callback) {
  setTimeout(function() {
    posts.push(post);
    callback();
  }, 2000);
}

function getPosts() {
  setTimeout(function() {
    let output = '';
    posts.forEach(function(post){
      output += `<li>${post.title}</li>`;
    });
    document.body.innerHTML = output;
  }, 1000);
}

createPost({title: 'Post Three', body: 'This is post three'}, getPosts);

از آنجایی که این کد را با هم و به تفصیل نوشتیم من دوباره آن ها را توضیح نمی دهم، برای اطلاعات بیشتر به قسمت callback ها مراجعه کنید. ما می خواهیم این کد را طوری ویرایش کنیم که به جای callback ها از promise ها استفاده کند. در قدم اول callback را از تابع createPost حذف کنید:

function createPost(post) {
  setTimeout(function() {
    posts.push(post);
  }, 2000);
}

برای استفاده از promise ها باید به شکل زیر عمل کنیم:

function createPost(post) {

  return new Promise(function (resolve, reject) {


  });

  setTimeout(function () {
    posts.push(post);
  }, 2000);
}

در واقع باید یک نمونه از شیء Promise را return کنیم. این شیء خودش به عنوان پارامتر یک تابع را دریافت می کند که دو پارامتر دارد:

  • resolve: زمانی آن را صدا می زنیم که کارمان تمام شده باشد.
  • reject: زمانی آن را صدا می زنیم که به مشکل یا خطایی برخورد کنیم.

در مرحله ی بعد تابع setTimeout را کات کرده و داخل promise قرار دهید:

function createPost(post) {

  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      posts.push(post);
    }, 2000);
  });
}

حالا دقیقا در جایی که قبلا callback را داشتیم، resolve را صدا می زنیم:

function createPost(post) {

  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      posts.push(post);
      resolve();
    }, 2000);
  });
}

سپس در انتهای این کد به جای پاس دادن getPosts می توانیم از then. استفاده کنیم و getPosts را به آن پاس بدهیم:

const posts = [
  { title: 'Post One', body: 'This is post one' },
  { title: 'Post Two', body: 'This is post two' }
];

function createPost(post) {

  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      posts.push(post);
      resolve();
    }, 2000);
  });
}

function getPosts() {
  setTimeout(function () {
    let output = '';
    posts.forEach(function (post) {
      output += `<li>${post.title}</li>`;
    });
    document.body.innerHTML = output;
  }, 1000);
}

createPost({ title: 'Post Three', body: 'This is post three' }).then(getPosts);

Promise ها به همین سادگی قابل درک هستند.

حالا اگر خطایی داشته باشیم می توانیم reject را صدا بزنیم اما در کد بالا که یک کد محلی است هیچ خطایی نداریم بنابراین من یک متغیر به نام error تعریف می کنم تا شرایط خطا را شبیه سازی کنیم:

function createPost(post) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      posts.push(post);

      const error = true;

      if (!error) {
        resolve();
      } else {
        reject('Error: Something went wrong');
      }
    }, 2000);
  });
}

در واقع با یک شرط if ساده گفته ایم که اگر خطا وجود داشت، کار را متوقف و reject کن اما اگر مشکلی نبود resolve کن (یعنی کدها اجرا بشود). حالا باید به انتهای فایل رفته و پس از then قسمتی به نام catch را نیز بنویسیم:

createPost({ title: 'Post Three', body: 'This is post three' }).then(getPosts).catch(function (err) {
  console.log(err);
});

در واقع then و catch را به هم چسبانده ایم. حالا با ذخیره ی کد بالا به خطای زیر می خوریم:

خطای حاصل از promise ما
خطای حاصل از promise ما

و اگر متغیر error را روی false بگذاریم دیگر خطایی نخواهیم داشت و کد به درستی کار می کند.

باز هم تکرار می کنم که یادگیری promise ها مهم است چرا که fetch api یک promise را برمی گرداند بنابراین باید با آن ها آشنا باشیم.

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

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.