جاوا اسکریپت Async: ساخت یک کتابخانه ی AJAX – قسمت 1

جاوا اسکریپت Async: ساخت یک کتابخانه ی AJAX – قسمت 1

ساخت یک کتابخانه AJAX – قسمت 1

در قسمت قبل با callback ها و مفاهیم اولیه ی مورد نیاز برای شروع این جلسه آشنا شدید. در این جلسه می خواهیم کارمان را شروع کرده و خودمان یک کتابخانه AJAX اختصاصی و ساده به نام Easy HTTP بسازیم. برای ساخت این کتابخانه، نیاز به یک سرور تمرینی داریم تا کد هایمان را با آن تست کنیم، اما نگران نباشید نیازی به خرید سرور نیست. ما می توانیم به راحتی از یک سرور تمرینی و رایگان به نام JSONPlaceholder استفاده نماییم. این سرور یک REST API تمرینی و رایگان است که از انواع متد های GET و POST و PUT و ... پشتیبانی می کند. برای آشنایی با این سرور به آدرس زیر بروید:

www.jsonplaceholder.typicode.com

در صفحه ی اصلی تمام توضیحات مربوط به این API را خواهید دید و تمام Endpoint ها به شما نمایش داده شده است (البته این سرور تمرینی به آن ها Route یا مسیر می گوید، هر دو یکی است). البته باید توجه داشته باشید که به دلایل امنیتی اجازه ی ثبت واقعی پست در پایگاه داده ی آن ها را نداریم بنابراین زمانی که از متد POST استفاده می کنیم، یک پیام success (موفقیت آمیز بودن درخواست) به ما ارسال می شود که حاوی پستی است که همانجا ارسال کرده ایم. در صورتی نیز که نحوه ی ارسال درخواست خطا باشد با یک پیام خطا روبرو خواهیم شد.

برای شروع کار یک پوشه به نام easyhttp ایجاد کرده و فایل های زیر را در آن ایجاد کنید:

  • index.html
  • easyhttp.js
  • app.js

فایل html مثل همیشه قالب صفحه را در خود دارد اما از بین دو فایل جاوا اسکریپتی بالا easyhttp.js حاوی کد های کتابخانه ی ما و app.js حاوی کد های استفاده از این کتابخانه خواهد بود. محتوای فایل HTML به شکل زیر خواهد بود:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>EasyHTTP Example</title>
</head>
<body>
  <h1>EasyHTTP Example</h1>

  <script src="easyhttp.js"></script>
  <script src="app.js"></script>
</body>
</html>

این صفحه تقریبا خالی است به دلیل اینکه اکثر کار ما با سرور تمرینی از طریق console مرورگر است و نیازی به کد های مفصل HTML نداریم. نکته ی مهم موجود در این فایل این است که فایل easyhttp.js باید قبل از App.js بارگذاری شود چرا که قرار است کد های درون app.js از کد های easyhttp استفاده کنند.

کتابخانه ی ما یک کتابخانه ی شیء گرای جاوا اسکریپتی از نوع ES5 خواهد بود بنابراین از prototype استفاده خواهد کرد. احتمالا از خودتان سوال کنید چرا از کلاس های ES6 استفاده نمی کنیم؟ من می خواهم این کتابخانه را یک بار با ES5 و با شیء XMLHttpRequest بنویسم و سپس از تکنولوژی جدید تر Fetch API استفاده کنم و همین کتابخانه را دوباره با کلاس های ES6 کدنویسی کنم تا همه چیز برایتان مرور شود.

حالا برای شروع کدنویسی وارد فایل easyhttp.js شوید. اولین کار ما تعریف تابع است:

function easyHTTP() {
    this.http = new XMLHttpRequest();
}

همانطور که می بینید این تابع تنها یک خصوصیت به نام http دارد که همان شیء XHR می باشد. ما می خواهیم برای این کتابخانه، چهار متد POST و GET و PUT و DELETE را تعریف کنیم بنابراین این قسمت ها را با کامنت مشخص می کنیم تا کد هایمان شلوغ نشود:

function easyHTTP() {
  this.http = new XMLHttpRequest();
}

// Make an HTTP GET Request

// Make an HTTP POST Request

// Make an HTTP PUT Request

// Make an HTTP DELETE Request

حالا باید با متد GET شروع کنیم:

// Make an HTTP GET Request
easyHTTP.prototype.get = function(url) {
  this.http.open('GET', url, true);
}

ما می خواهیم کاربر خودش endpoint یا url را تعیین کند بنابراین آن را به صورت یک پارامتر به متد get پاس داده ایم (اگر با این syntax آشنا نیستید به دوره ی «جاوا اسکریپت شیء گرا» مراجعه کنید). حالا باید کد های onload (هنگام دریافت پاسخ) را بنویسیم بنابراین می گوییم:

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

  this.http.onload = function() {

  }

  this.http.send();
}

مثل همیشه برای تعریف یک متد، می توانیم آن را به صورت یک تابع ساده بنویسیم. البته من در همین قسمت دستور send را نیز ارسال کرده ام تا بعدا یادمان نرود. در این مرحله ممکن است بخواهید مثل همیشه status را چک کنید تا روی 200 باشد اما روش قبلی دیگر در اینجا کار نمی کند! بگذارید به شما نشان دهم که چرا چنین روشی کار نخواهد کرد. من کد ها را به شکل معمول و همیشگی می نویسم:

function easyHTTP() {
    this.http = new XMLHttpRequest();
}

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

    this.http.onload = function () {
        if (this.http.status === 200) {
            console.log(this.http.responseText);
        }
    }

    this.http.send();
}

حالا وارد فایل app.js شوید تا از این کد ها استفاده کنیم. درون app.js می گوییم:

const http = new easyHTTP;

// Get Posts
http.get('https://jsonplaceholder.typicode.com/posts');

یعنی از easyHTTP یک نمونه می سازیم تا بتوانیم از آن استفاده کنیم، سپس یک درخواست get ساده به endpoint مشخص شده در سرور تمرینی JSONPlaceholder ارسال می کنیم. به نظر شما درون کنسول مرورگر چه چیزی دریافت می کنیم؟

status برای ما تعریف نشده است!
status برای ما تعریف نشده است!

این همان خطایی است که به شما می گفتم، status برای this.http تعریف نشده است! آیا می توانید مشکل را حدس بزنید؟ این همان مشکل معروف this در جاوا اسکریپت است. اگر درون یک تابع باشید، کلیدواژه ی this به خود تابع اشاره خواهد کرد:

    this.http.onload = function () {
        if (this.http.status === 200) {
            console.log(this.http.responseText);
        }
    }

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

    this.http.onload = () => {
        if (this.http.status === 200) {
            console.log(this.http.responseText);
        }
    }

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

نتیجه ی استفاده از arrow function ها
نتیجه ی استفاده از arrow function ها

من می خواهم روش ES5 را به شما یاد بدهم که استفاده از یک متغیر دیگر است. معمولا این متغیر نام self یا that را می گیرد و به روش زیر انجام می شود:

    let self = this;
    this.http.onload = function () {
        if (self.http.status === 200) {
            console.log(self.http.responseText);
        }
    }

با استفاده از این روش متغیر self همان scope صحیح را می گیرد چرا که خارج از تابع تعریف شده است و سپس با قرار دادن آن به جای this به scope صحیح اشاره خواهیم کرد. حالا کد های ما تصحیح شده اند.

همچنین ممکن است تصور کنید که می توانید به جای console کردن پاسخ سرور، آن را return کنید:

    let self = this;
    this.http.onload = function () {
        if (self.http.status === 200) {
            return this.http.responseText;
        }
    }

و سپس به app.js بروید و بگویید:

const http = new easyHTTP;

// Get Posts
const posts = http.get('https://jsonplaceholder.typicode.com/posts');
console.log(posts);

اما اگر این کار را انجام دهید به خطای undefined برمی خورید! چرا؟ به دلیل اینکه داده ها قبل از اینکه از سرور به ما برسند، log شده اند بنابراین هنوز چیزی نداشته ایم که بخواهیم نمایش دهیم و مقدار undefined برایمان برگردانده می شود. اینجاست که callback ها وارد عمل می شوند!

برای حل این مشکل علاوه بر url یک callback را هم دریافت خواهیم کرد و به جای return کردن، آن را به عنوان پارامتر به تابع callback می دهیم:

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

    let self = this;
    this.http.onload = function () {
        if (self.http.status === 200) {
            callback(self.http.responseText);
        }
    }

    this.http.send();
}

حالا می توانیم به app.js برویم:

const http = new easyHTTP;

// Get Posts
http.get('https://jsonplaceholder.typicode.com/posts', function (response){
    console.log(response);
});

حالا کد های ما به سادگی کار می کند. یادتان باشد که response فقط یک نام است و می توانید به جای آن از هر نام دیگری استفاده کنید. این response همان self.http.responseText خواهد بود که به صورت اتوماتیک پاس داده خواهد شد.

حالا برای کامل تر شدن کار بهتر است خطا های احتمالی را نیز ارسال کنیم:

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

من پارامتر اول را برای خطا قرار می دهم و مقدار آن را null گذاشته ام، سپس یک قسمت else اضافه کرده ام (یعنی اگر status برابر 200 نبود و درخواست مشکل داشت) کد خطا نیز ارسال خواهد شد. حالا به فایل app.js می رویم و از این خطا استفاده می کنیم:

// Get Posts
http.get('https://jsonplaceholder.typicode.com/posts', function(err, posts) {
  if(err) {
    console.log(err);
  } else {
    console.log(posts);
  }
});

به جای response نام posts را انتخاب کرده ام و با یک شرط مشخص کرده ام که اگر خطایی وجود داشت، آن خطا Console.log شود. در قسمت های بعدی، متد های دیگر مانند POST را نیز تکمیل می کنیم.

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

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

سید علی
11 فروردین 1400
سلام دمتون گرم بسیار عالی واقعا Ajax واقعی به این میگین نه چهار تا تابع ،که به ما یاد دادن که صرفا یک شی دست کنیم باید از اول این دوره تا اخرش مطالعه کنم واقعا ممنون

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