مطالب
مبانی TypeScript؛ ماژول‌ها
تاریخچه

تا پیش از نگارش 1.5 تایپ اسکریپت، مفاهیم internal modules و external modules وجود داشتند. جهت نامگذاری بهتر و کاهش سردرگمی در استفاده‌ی آن‌ها، از نگارش 1.5 به بعد، ماژول‌های داخلی به namespaces (فضاهای نام) تغییر نام یافتند و ماژول‌های خارجی به نام «ماژول» خلاصه شدند.
همچنین از نگارش 1.5 به بعد، پشتیبانی کاملی از نحوه‌ی تعریف «ماژول‌ها در ES 6» نیز به عمل می‌آید. بنابراین مطالعه‌ی آن نیز پیشنهاد می‌گردد.


مفهوم ماژول‌ها

هدف اصلی از ماژول‌ها، ارائه‌ی روشی برای مدیریت و ساماندهی پروژه‌های بزرگ با تعداد فایل‌های زیاد است. در اینجا فایل‌های ارجاعی، در زمان اجرا، توسط runtime جاوا اسکریپت بارگذاری شده و سپس به امکانات آن‌ها دسترسی خواهیم داشت. ماژول‌ها به صورت توکار در Node.JS نیز پشتیبانی می‌شوند؛ البته با فرمت common.js که کامپایلر TypeScript نیز قادر به تولید آن است.


امکان کامپایل به روش‌های قدیمی‌تر تعریف ماژول‌ها در TypeScript

در مورد انواع روش‌های قدیمی‌تر نحوه‌ی تعریف ماژول‌های در جاوا اسکریپت مانند common.js، AMD و امثال آن‌ها، مطالعه‌ی مطلب «ماژول‌ها در ES 6» توصیه می‌شود. فقط نکته‌ای که در اینجا حائز اهمیت است، این است که چون TypeScript قادر است به ES 5 نیز کامپایل شود و در ES 5 روش جدید ES 6 جهت تعریف ماژول‌ها وجود ندارد، امکان تبدیل و ترجمه‌ی کدهای TypeScript به تمام نوع‌های معروف و شناخته شده‌ی ماژول‌ها مانند common.js توسط کامپایلر TypeScript به صورت خودکار وجود دارد. برای این منظور از سوئیچ module کامپایلر استفاده می‌شود.


نحوه‌ی تعریف ماژول‌ها در TypeScript

برای تبدیل یک فایل ts به یک ماژول، تنها کافی است موردی را از آن export کنیم. آیتم‌های موجود در یک ماژول، تنها زمانی در سایر فایل‌ها قابل استفاده خواهند بود که از آن export شده باشند:
 // periodicals.ts
export interface Periodical {
   issueNumber: number;
}

export class Magazine implements Periodical {
   issueNumber: number;
}

export function GetMagazineByIssueNumber(issue: number): Magazine {
   // retrieve and return a magazine
}
در این مثال، یک اینترفیس، کلاس و متد export شده‌اند. برای این منظور واژه‌ی کلیدی export به پیش از هر کدام از آیتم‌های مدنظر اضافه شده‌است.
روش دیگر انجام این تعاریف، حذف واژه‌ی کلیدی export از تمام موارد تعریف شده و سپس خلاصه کردن آن‌ها در یک سطر، توسط روش export statement است؛ به نحو ذیل:
 // periodicals.ts
interface Periodical {
   issueNumber: number;
}

class Magazine implements Periodical {
   issueNumber: number;
}

function GetMagazineByTitle(title: string): Magazine {
   // retrieve and return a magazine
}

export { Periodical, Magazine, GetMagazineByTitle as GetMag}
مزیت این روش، مشخص بودن محل تعاریف خروجی‌ها است؛ بدون اینکه نیازی باشد تا تمام فایل‌را جهت یافتن exportها جستجو کرد.
همچنین در اینجا می‌توان نام دیگری را نیز برای خروجی‌ها درنظر گرفت. برای مثال بجای نام GetMagazineByTitle، با استفاده از as syntax، یک نام جدید معرفی شده‌است.


نحوه‌ی استفاده‌ی از ماژول‌ها در TypeScript

برای استفاده‌ی از امکانات خروجی مثال قبل، در یک ماژول دیگر، به نحو ذیل عمل می‌کنیم:
 // news.ts
import { Magazine, GetMag as GetMagazine} from './periodicals';
let newsMag: Magazine = GetMagazine('Weekly News');
در اینجا پس از تعریف واژه‌ی کلیدی import، لیست موارد مدنظر از خروجی‌های فایل periodicals را داخل یک {} می‌توان قید کرد. بنابراین نیازی نیست تا تمام خروجی‌های یک ماژول را import کرد. همچنین در اینجا نیز با استفاده از as syntax می‌توان نام جدیدی را برای موارد import شده تعیین کرد.
در انتها نیز مسیر نسبی فایل ts ماژول، بدون ذکر پسوند آن، پس از واژه‌ی کلیدی from ذکر می‌شود.

اگر نیاز است تمام خروجی‌های یک ماژول به صورت خودکار import شوند، می‌توان از * استفاده کرد:
 // kids.ts
import * as mag from './periodicals';
اینبار با توجه به as syntax استفاده شده، نحوه‌ی دسترسی به خروجی‌های ماژول مدنظر به صورت ذیل خواهد بود (ابتدا ذکر نام alias تعریف شده، به همراه یک دات):
 let kidMag: mag.Magazine= mag.GetMag('Games and Stuff!');


خروجی پیش فرض یک ماژول

اگر تنها قرار است یک آیتم از ماژولی export شود، می‌توان از مفهوم default export استفاده کرد:
 // movie.ts
export default class{
   title: string;
   director: string;
}
در این مثال export default بر روی یک کلاس بدون نام تعریف شده‌است. تعریف نام کلاس در اینجا اختیاری است و ماژول import کننده‌ی آن نیازی به دانستن این نام ندارد؛ زیرا در این حالت import کننده می‌تواند نام دلخواهی را به این خروجی پیش فرض بدهد؛ مانند AnimatedMovie بدون نیاز به ذکر {}:
 // kids.ts
import AnimatedMovie from './movie’;
let cartoon = new AnimatedMovie();
مطالب
Reflection در ES6
در زبان‌های برنامه‌نویسی مانند سی‌شارپ و یا جاوا می‌توانیم از Reflection جهت خواندن متادیتاها استفاده کنیم. به عنوان مثال امکان تعریف پراپرتی و یا متدها و حتی تایپ‌هایی در زمان اجرا را در اختیارمان قرار می‌دهد. اما از آنجائیکه جاوا اسکریپت یک زبان داینامیک است، این قابلیت کمتر مورد توجه قرار گرفته است. در جاوا اسکریپت حین کار با کلاس‌ها و اشیاء، ممکن است نیاز داشته باشید تا از اعضای یک کلاس کوئری بگیرید و یا اینکه یک سری پراپرتی و متدهایی را در زمان اجرا به اشیاء‌تان اضافه کنید و یا مواردی از این دست.
تا قبل از ES 6 می‌توانستیم به این صورت به اعضای یک کلاس دسترسی داشته باشیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (var prop in person) {
    console.log(prop); 
}
همانطور که مشاهده می‌کنید، توسط سینتکس for...in می‌توانیم به اعضای person دسترسی داشته باشیم. همچنین برای دسترسی به مقادیر هر کدام از اعضای آن می‌توانستیم از bracket syntax استفاده کنیم:
for (var prop in person) {
    console.log(person[prop]); 
}
لازم به ذکر است می‌توانستیم از متدهایی همانند Array.isArray, Object.getOwnPropertyDescriptor و حتی Object.keys نیز جهت دسترسی به اعضای پنهان یک شیء استفاده کنیم. اکنون قابلیت توکاری با نام Reflect این امکان را به ES 6 اضافه کرده است تا تمام اعمال فوق را به راحتی انجام داد.

Reflect
همانطور که عنوان شد توسط API جدیدی با نام Reflect، می‌توانیم به سادگی اعمال مربوط به Reflection را انجام دهیم. به عنوان مثال برای دسترسی به اعضای شیء person با استفاده از این API می‌توانیم به این صورت عمل کنیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (let prop of Reflect.enumerate(person)) {
    console.log(`the value for ${prop} is ${person[prop]}`); 
  
}
در کد فوق از متد enumerate استفاده شده است، این متد یک پارامتر target را از ورودی می‌پذیرد. در واقع target همان شیءایی است که می‌خواهید پراپرتی‌های آن را پیمایش کنید. همچنین مقدار بازگشتی این متد یک iterator می‌باشد. مفهوم iterator نیز خیلی ساده است. به زبان ساده، امکان پیمایش درون یک کالکشن را در اختیارمان قرار می‌دهد. نکته‌ی دیگری که در کد فوق وجود دارد، استفاده از سینتکس for..of است، این سینتکس شبیه به for...in عمل می‌کند، با این تفاوت که در اینجا به جای پیمایش درون یکسری keys، درون values پیمایش می‌کنیم. در نتیجه برای پیمایش iterators باید از این سینتکس استفاده شود.

متدهای شیء Reflect
در ادامه تعدادی از متدهای شیء Reflect را مشاهده می‌کنید:
Reflect.get(target, name, [receiver])

Reflect.set(target, name, value, [receiver])

Reflect.has(target, name)

Reflect.apply(target, receiver, args)

Reflect.construct(target, args)

Reflect.getOwnPropertyDescriptor(target, name)

Reflect.defineProperty(target, name, desc)

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, newProto)

Reflect.deleteProperty(target, name)

Reflect.enumerate(target)

Reflect.preventExtensions(target)

Reflect.isExtensible(target)

Reflect.ownKeys(target)
تعدادی از متدهای فوق خیلی مشابه متدهای Object هستند؛ با این تفاوت که متدهای فوق اطلاعات بهتری را بر می‌گردانند. به عنوان مثال متد Reflect.defineProperty در صورت ایجاد شدن یک پراپرتی جدید، مقدار true را برمی‌گرداند:
var yay = Reflect.defineProperty(target, 'foo', { value: 'bar' })
if (yay) {
  // yay!
} else {
  // oops.
}
اگر همین کار را با استفاده از متد Object.defineProperty انجام می‌دادیم می‌بایست کد را درون بلاک try/catch می‌نوشتیم:
try {
  Object.defineProperty(target, 'foo', { value: 'bar' })
  // yay!
} catch (e) {
  // oops.
}
به عنوان یک مثال دیگر، قبلاً برای حذف یک پراپرتی از یک شیء از کلمه کلیدی delete به اینصورت استفاده می‌کردیم:
var target = { foo: 'bar', baz: 'wat' }
delete target.foo
console.log(target)
// <- { baz: 'wat' }
اما با کمک متد Reflect.deleteProperty به راحتی می‌توانیم یک پراپرتی را حذف کنیم:
var target = { foo: 'bar', baz: 'wat' }
let isDeleted = Reflect.deleteProperty(target, 'foo')
console.log(isDeleted );
// true
در این‌حالت همانند متد Reflect.defineProperty، در صورت موفقیت‌آمیز بودن عمل حذف، مقدار true برگردانده می‌شود.
برای مشاهده‌ی لیست کامل متدهای فوق می‌توانید به اینجا مراجعه کنید. همچنین می‌توان با مراجعه به قسمت Browser compatibility وضعیت پشتیبانی هر کدام را در مرورگرهای مختلف مشاهده کرد.

نکاتی که در حین کار کردن با Reflect باید در نظر بگیرید:
  • این شیء فاقد متد [[Construct]] می‌باشد. یعنی نمی‌تواند همراه با کلمه‌ی کلیدی new مورد استفاده قرار گیرد.
  • همچنین نمی‌توان آن را همانند یک تابع فراخوانی کرد.
  • تمام متدهای آن به صورت استاتیک تعریف شده‌اند (همانند شیء Math).
مطالب
آموزش QUnit #1
مقدمه:
تست و آزمایش کد برنامه‌ها و وب سایت‌هایمان، بهترین راه کاهش خطا و مشکلات آنها بعد از انتشار است. از جمله روش‌های موجود، تست واحد است که ویژوال استادیو نیز از آن برای پروژه‌های دات نت پشتیبانی می‌کند. با افزایش روز افزون کتابخانه‌های جاوا اسکریپتی و جی کوئری، نیاز به تست کد‌های جاواسکریپتی نیز بیشتر به نظر می‌رسد و بهتر است تست واحد و آزمایش شوند. اما برخلاف کدهای #C و ASP.NET تست کد‌های جاوا اسکریپت، مخصوصا زمانی که به دستکاری عناصر DOM می‌پردازیم و یا رویداد‌های درون صفحه وب را با استفاده از جی کوئری می‌نویسیم، حتی اگر در فایل جداگانه‌ای نوشته شود، این بدان معنی نیست که آماده تست واحد است و ممکن است امکان نوشتن تست وجود نداشته باشد.
بنابراین چه چیزی یک تست واحد است؟ در بهترین حالت توابعی که مقداری را برمی گردادنند، بهترین حالت برای تست واحد است. اما در بیشتر موارد شما نیاز دارید تا تاثیر کد را بر روی عناصر صفحه نیز مشاهد نمایید.
ساخت تست واحد
برای تست پذیری بهتر، توابع جاوا اسکریپت و هر کد دیگری، آن را می‌بایست طوری بنویسید که مقادیر تاثیر گذار در اجرای تابع به عنوان ورودی تابع در نظر گرفته شده باشند و همیشه نتیجه به عنوان خروجی تابع برگردانده شود؛ قطعه کد زیر را در نظر بگیرید:
 function prettyDate(time){
    var date = new Date(time || ""),
      diff = (((new Date()).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff == 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  }
تابع pertyDate اختلاف زمان حال را نسبت به زمان ورودی، بصورت یک رشته برمی گرداند. اما در اینجا مقدار زمان حال، در خط سوم، در خود تابع ایجاد شده است و در صورتی که بخواهیم برای چندین مقدار آن را تست کنیم زمان حال متفاوتی در نظر گرفته می‌شود و حداکثر، زمان 31 روز قبل را نمایش داده و در بقیه تاریخ ها undefined را بر می‌گرداند. برای تست واحد، چند تغییر می‌دهیم.
بهینه سازی، مرحله اول:
پارامتری به عنوان مقدار زمان جاری برای تابع در نظر می‌گیریم و تابع را جدا کرده و در یک فایل جداگانه قرار می‌دهیم. فایل prettydate.js بصورت زیر خواهد شد.
function prettyDate(now, time){
  var date = new Date(time || ""),
    diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
    day_diff = Math.floor(diff / 86400);
 
  if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
    return;
 
  return day_diff == 0 && (
      diff < 60 && "just now" ||
      diff < 120 && "1 minute ago" ||
      diff < 3600 && Math.floor( diff / 60 ) +
        " minutes ago" ||
      diff < 7200 && "1 hour ago" ||
      diff < 86400 && Math.floor( diff / 3600 ) +
        " hours ago") ||
    day_diff == 1 && "Yesterday" ||
    day_diff < 7 && day_diff + " days ago" ||
    day_diff < 31 && Math.ceil( day_diff / 7 ) +
      " weeks ago";
}
حال یک تابع برای تست داریم، چند تست واحد واقعی می‌نویسیم
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <script src="prettydate.js"></script>
  <script>
  function test(then, expected) {
    results.total++;
    var result = prettyDate("2013/01/28 22:25:00", then);
    if (result !== expected) {
      results.bad++;
      console.log("Expected " + expected +
        ", but was " + result);
    }
  }
  var results = {
    total: 0,
    bad: 0
  };
  test("2013/01/28 22:24:30", "just now");
  test("2013/01/28 22:23:30", "1 minute ago");
  test("2013/01/28 21:23:30", "1 hour ago");
  test("2013/01/27 22:23:30", "Yesterday");
  test("2013/01/26 22:23:30", "2 days ago");
  test("2012/01/26 22:23:30", undefined);
  console.log("Of " + results.total + " tests, " +
    results.bad + " failed, " +
    (results.total - results.bad) + " passed.");
  </script>
</head>
<body>
 
</body>
</html>
در کد بالا یک تابع بدون استفاده از Qunit برای تست واحد نوشته ایم که با آن تابع prettyDate را تست می‌کند. تابع test مقدار زمان حال و رشته خروجی را گرفته  و آن را با تابع اصلی تست می‌کند در آخر تعداد تست ها، تست‌های شکست خورده و تست  های پاس شده گزارش داده می‌شود.
خروجی می‌تواند مانند زیر باشد:

Of 6 tests, 0 failed, 6 passed.
Expected 2 day ago, but was 2 days ago. 
f 6 tests, 1 failed, 5 passed.

مطالب
برنامه نویسی Async با ES 6
جاوا اسکریپت به صورت single-thread عمل می‌کند. به این معنا که دو اسکریپت نمی‌توانند به صورت همزمان اجرا شوند و باید یکی پس از دیگری اجرا شوند. ساده‌ترین شکل برنامه‌نویسی غیرهمزمان در جاوا اسکریپت استفاده از callback می‌باشد. به عنوان مثال در سناریوی زیر Caller یکسری عملیات غیرهمزمان را مانند یک فراخوانی XHR و یا یک تایمر، انجام می‌دهد. زمانیکه Caller عملیات غیرهمزمانی را آغاز کرد، یک callback را به آن ارسال خواهد کرد و بعد از مطمئن شدن از موفق بودن عملیات، callback را فراخوانی می‌کند. بعد از پایان عملیات، callback درون call stack قرار خواهد گرفت و هر وقت که بقیه‌ی عملیات به اتمام رسید، اجرا خواهد شد.

این روش چندین مشکل دارد:
  • تنها caller از پایان یافتن عملیات غیرهمزمان مطلع خواهد شد.
  • هندل کردن خطا و همچنین مدیریت چندین عملیات asynchronous به صورت همزمان خیلی سخت خواهد بود.
در اینحالت callback باید به چندین کار رسیدگی کند:
  • پردازش نتایج فراخوانی‌های async
  • اجرای دیگر عملیات براساس پاسخ
کد زیر را در نظر بگیرید:
function getCompanyFromOrderId(orderId) {
    getOrder(orderId, function(order) {
        getUser(order.userId, function(user) {
            getCompany(user.companyId, function(company) {
                // do something with company
            });
        });
    });
}
در کد فوق به اطلاعات یک شرکت براساس شماره سفارش آن دسترسی خواهیم داشت. پس از دریافت سفارش، اطلاعات کاربر را دریافت خواهیم کرد. پس از پایان آن، می‌توانیم به اطلاعات شرکت دسترسی داشته باشیم. این سبک نوشتن کدها به صورت تودرتو خوانایی/ نگهداری کد را کاهش خواهد داد. همانطور که مشاهده می‌کنید callback نه تنها پاسخ بلکه یک callback دیگر را هربار به تابع بعدی ارسال می‌کند. در این‌حالت اگر بخواهیم استثناء‌ها را نیز مدیریت کنیم کدها به مراتب پیچیده‌تر خواهند شد:
function getCompanyFromOrderId(orderId) {
    try {
        getOrder(orderId, function (order) {
            try {
                getUser(order.userId, function (user) {
                    try {
                        getCompany(user.companyId, function (company) {
                            try {
                                // do something with company
                            } catch (ex) {
                                // handle exception
                            }
                        });
                    } catch (ex) {
                        // handle exception
                    }
                });
            } catch (ex) {
                // handle exception
            }
        });
    } catch (ex) {
        // handle exception
    }
}
ممکن است بگوئید که نیازی به این همه try/catch در کد فوق نیست. اما هر callback ایی که به صورت مجزا وارد call stack می‌شود، هر خطایی که صادر شود توسط شروع کننده‌ی اصلی درخواست async به دام انداخته نخواهد شد و در نتیجه نوشتن این همه try/catch ضروری است. لازم به ذکر است، به این نوع نوشتن callback به صورت تودرتو callback hell و یا pyramid of doom نیز گفته می‌شود.

راه‌حل: استفاده از Promises
Promises همیشه به عنوان یک راه‌حل برای callback hell شناخته شده هستند. Promises در واقع اشیایی هستند که این اطمینان را به شما خواهند داد تا بعد از پایان یک عملیات غیرهمزمان، پاسخ را صرفنظر از اینکه عملیات fail و یا success شده باشد، در اختیارتان قرار خواهند داد. یک Promise از دو قسمت تشکیل شده است:
  • Control
  • Promise
قسمت اول یا Control در بیشتر کتابخانه‌ها با نام Deferred نیز از آن نامبرده می‌شود و در واقع یک شیء مستقل است. در بعضی از پیاد‌ه‌سازی‌ها این شیء در واقع خودش یک callback است. قسمت دوم نیز خود Promise است. این شیء می‌تواند دیگر قسمت‌های کد را از پایان یافتن عملیات غیرهمزمان مطلع سازد.
یک Promise می‌تواند یکی از حالت‌های زیر را داشته باشد:
  • pending: یعنی وضعیت اولیه، هنوز به پایان نرسیده است.
  • fulfilled: یعنی عملیات با موفقیت پایان پذیرفته است.
  • rejected: یعنی عملیات با شکست مواجه شده است.
با ایجاد یک Promise، وضعیت آن در اولین مرحله و pending خواهد بود. سپس تبدیل به یکی از وضعیت‌های fulfilled و یا rejected خواهد شد.

اکنون اگر بخواهیم کد قبلی را با استفاده از Promises پیاده‌سازی کنیم به این چنین نتیجه‌ایی خواهیم رسید:
function getCompanyFromOrderId(orderId) {
    getOrder(orderId).then(function(order) {
        return getUser(order.orderId);
    }).then(function(user) {
        return getCompany(user.companyId);
    }).then(function(company) {
        // do something with company
    }).then(undefined, function(error) {
        // handle error
    })
}
در اینجا به جای استفاده از callback در تابع getCompanyFromOrderId یک promise را برمی‌گردانیم. وقتی تابع getOrder با موفقیت کارش را انجام داد، callback بعدی اجرا خواهد شد که در واقع تابع getUser را فراخوانی می‌کند. یک عملیات غیرهمزمان دیگر نیز یک promise را برمی‌گرداند. بعد از آن calback دیگری فراخوانی خواهد شد و به همین ترتیب... در نهایت نیز توسط یک then دیگر می‌توانیم خطاها را هندل کنیم. بنابراین در این‌حالت کد فوق خواناتر و نگهداری آن به مراتب ساده‌تر از حالت قبل می‌باشد.
promises خیلی وقت است که در قالب کتابخانه‌های third-party مانند QwhenWinJSRSVP.js در اختیار برنامه‌نویسان جاوا اسکریپت قرار دارد. در نتیجه کد فوق به صورت جنریک است؛ به این معنا که با هر کدام از کتابخانه‌های عنوان شده سازگاری دارد.

نحوه‌ی ایجاد Promise
ساختار اولیه برای ایجاد یک promise به اینصورت است:
var promise = new Promise(function(resolve, reject) {
  // انجام یکسری عملیات به عنوان مثال دریافت اطلاعات از سرور و...

  if (/* اگر کدهای فوق با موفقیت انجام شدند */) {
    resolve("عملیات با موفقیت انجام پذیرفت");
  }
  else {
    reject(Error("خطایی رخ داده است"));
  }
});
همانطور که مشاهده می‌کنید سازنده‌ی promise یک callback را از ورودی دریافت خواهد کرد. این callback نیز از ورودی دو پارامتر را دریافت میکند: resolve و reject. درون callback می‌توانیم کدهایمان را بنویسیم. بعد از اینکه عملیات با موفقیت انجام گرفت resolve را فراخوانی خواهیم کرد. در غیراینصورت reject را فراخوانی می‌کنیم. همچنین به جای استفاده از throw درون reject از شیء Error استفاده کرده‌ایم. زیرا بعداً برای دیباگ خطا می‌توانیم به اطلاعات کامل stack trace دسترسی داشته باشیم.
در ادامه نحوه‌ی استفاده از promise فوق را مشاهده می‌کنید:
promise.then(function(result) {
  console.log(result); // "عملیات با موفقیت انجام پذیرفت "
}, function(err) {
  console.log(err); // Error: "خطایی رخ داده است"
});
در اینجا تابع then دو آرگومان را از ورودی دریافت خواهد کرد؛ یکی برای حالت success و  دیگری برای حالت failure. لازم به ذکر است، هر دوی این آرگومان‌ها اختیاری هستند.
به عنوان یک مثال عملی می‌توانیم متد get جی‌کوئری را به این صورت درون یک Promise قرار دهیم:
function get(url){
    return new Promise(function(resolve, reject) {
       $.get(url, function(data) {
           resolve(data);
       }) 
       .fail(function(){
          reject(); 
       });
    });
}
به اینصورت می‌توانیم از Promise فوق استفاده کنیم:
get('users.all').then(function(users){
    myController.users = users;
}, function(){
   delete myController.users; 
});
اگر هم مایل بودید می‌توانید به جای ارائه‌ی آرگومان دوم درون callback برای به دام انداختن خطاها از catch استفاده کنید:
get('users.all').then(function(users){
    myController.users = users;
})
.catch(function(){
    delete myController.users;
});

در شرایطی ممکن است بخواهیم بعد از اینکه تمامی Promise هایمان کارشان به اتمام رسید، یکسری عملیات دیگر را انجام دهیم:
var usersPromise = get('users.all');
var postsPromise = get('posts.everyone');

Promise.all([usersPromise, postsPromise])
.then(function(result){
    myController.users = result[0];
    myController.posts = result[1];
}, function(){
   delete myController.users;
   delete myController.posts; 
});
لازم به ذکر است اگر حتی یکی از Promiseها reject شود Promise اصلی نیز reject خواهد شد.

اگر خروجی then به صورت رشته‌ایی باشد چه اتفاقی خواهد افتاد؟
در حالت کلی خروجی هر then. به then بعدی پاس داده خواهد شد. به عنوان مثال در کد زیر نتایج به صورت رشته‌ایی برگردانده خواهند شد و می‌توانیم آن‌ها را به سادگی توسط JSON.parse به then بعدی ارسال کنیم:
get('users.all').then(function(usersString){
    return JSON.parse(usersString);
}).then(function(users){
   myController.users = users; 
});
و یا به صورت خلاصه‌تر می‌توانیم به اینصورت اینکار را انجام دهیم:
get('users.all').then(JSON.parse).then(function(users){
   myController.users = users; 
});
برای مشاهده‌ی دیگر متدهای استاتیک Promise می‌توانید به اینجا مراجعه نمائید.
مطالب
لینک‌های هفته‌ی سوم بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

Visual Studio

ASP. Net


طراحی و توسعه وب

PHP

اس‌کیوال سرور

سی شارپ

عمومی دات نت

ویندوز

مسایل اجتماعی و انسانی برنامه نویسی

متفرقه
مطالب
نصب یک اسمبلی دات نت در GAC

افزونه فارسی به پارسی را قبل از ارائه در سایت، بر روی یک ماشین مجازی هم تست کردم. برای این منظور از Microsoft virtual pc استفاده شد. البته در مقابل امکانات VMware شاید حرفی برای گفتن نداشته باشد ولی خوب جهت مقاصد تست نرم افزار بر روی یک سیستم عاری از وسایل برنامه نویسی مناسب است. (برای نصب یک سیستم عامل توسط آن برای مثال می‌شود از سی دی آن OS یک فایل ISO تهیه کرد و مسیر این فایل ISO را به ماشین مجازی معرفی کرد . سپس سیستم بوت شده و روال نصب مطابق معمول خواهد بود)
اولین مشکلی که پس از تست بر روی سیستم مجازی رخ داد، پیغام یافت نشدن اسمبلی مربوط به SQLite بود. نرم افزار word هنگام اجرای افزونه‌های دات نت، آنها را در مسیری با یک نامگذاری منحصربفرد کپی می‌کند و تنها هم همان اسمبلی افزونه را کپی می‌کند و نه سایر موارد همراه را. برای پیدا کردن این مسیر می‌شود از روش زیر استفاده کرد:
using System.Reflection;
Assembly.GetExecutingAssembly().Location

در این مسیر اسمبلی SQLite وجود ندارد و به همین دلیل هم بارگذاری نخواهد شد. بهترین راه حل برای رفع این مشکل، نصب اسمبلی مربوطه در GAC یا global assembly cache است.
برای نصب اسمبلی در GAC استفاده از برنامه gacutil توصیه شده است. این برنامه به همراه SDK دات نت فریم ورک ارائه می‌شود و الزامی ندارد که کاربر نهایی این برنامه را داشته باشد. خوشبختانه با استفاده از برنامه نویسی هم می‌شود یک نمونه از برنامه Gacutil را خودمان ایجاد کنیم (برای مثال ایجاد یک برنامه کنسول و دریافت مسیر از طریق آرگومان‌های ارسالی به آن):

new System.EnterpriseServices.Internal.Publish().GacInstall(path);

در اینجا باید ارجاعی از System.EnterpriseServices نیز به برنامه اضافه شود.
این روش در مورد اسمبلی SQLite که دارای امضای دیجیتال است کار خواهد کرد. اما اگر قصد داشته باشید به صورت عمومی از آن استفاده کنید، باید ابتدا بررسی کرد که آیا فایل اسمبلی دارای امضای دیجیتال است یا خیر. برای این منظور می‌توان مقدار عبارت زیر را ارزیابی کرد:
Assembly.LoadFile(path).GetName().GetPublicKey().Length

اگر این طول بزرگتر از صفر بود به این معنا است که فایل اسمبلی دارای امضای دیجیتال است و می‌توان آنرا در GAC نصب کرد.
لازم به ذکر است که متد معرفی شده برای نصب در GAC در صورت عدم موفقیت هیچ پیغام خطا یا exception ایی را در برنامه تولید نخواهد کرد. اما پیغام خطای حاصل را در event log ویندوز می‌توان مشاهده کرد.


مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
پیاده سازی Unobtrusive Ajax را در ASP.NET MVC 5.x، می‌توانید در مطلب «ASP.NET MVC #21» مطالعه کنید. HTML Helpers مرتبط با Ajax، به طور کامل از ASP.NET Core 1.0 حذف شده‌اند. اما این مورد به این معنا نیست که نمی‌توان Unobtrusive Ajax را در ASP.NET Core که تمرکزش بیشتر بر روی Tag Helpers جدید هست تا HTML Helpers قدیمی، پیاده سازی کرد.


Unobtrusive Ajax چیست؟

در حالت معمولی، با استفاده از متد ajax جی‌کوئری، کار ارسال غیرهمزمان اطلاعات، به سمت سرور صورت می‌گیرد. چون در این روش کدهای جی‌کوئری داخل صفحات برنامه‌های ما قرار می‌گیرند، به این روش، «روش چسبنده» می‌گویند. اما با استفاده از افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، می‌توان این کدهای چسبنده را تبدیل به کدهای غیرچسنبده یا Unobtrusive کرد. در این حالت، پارامترهای متد ajax، به صورت ویژگی‌ها (attributes) به شکل data-ajax به المان‌های مختلف صفحه اضافه می‌شوند و به این ترتیب، افزونه‌ی یاد شده به صورت خودکار با یافتن مقادیر ویژگی‌های data-ajax، این المان‌ها را تبدیل به المان‌های ای‌جکسی می‌کند. در این حالت به کدهایی تمیزتر و عاری از متدهای چسبنده‌ی ajax قرار گرفته‌ی در داخل صفحات وب خواهیم رسید.
روش طراحی Unobtrusive را در کتابخانه‌های معروفی مانند بوت استرپ هم می‌توان مشاهده کرد.


پیشنیازهای فعال سازی Unobtrusive Ajax در ASP.NET Core 1.0

توزیع افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، از طریق bower صورت می‌گیرد که پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران» با آن آشنا شدیم. در اینجا نیز برای دریافت آن، تنها کافی است فایل bower.json را به نحو ذیل تکمیل کرد:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
   "bootstrap": "3.3.6",
   "jquery": "2.2.0",
   "jquery-validation": "1.14.0",
   "jquery-validation-unobtrusive": "3.2.6",
   "jquery-ajax-unobtrusive": "3.2.4"
  }
}
و پس از آن فایل bundleconfig.json مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification» یک چنین شکلی را پیدا می‌کند:
[
  {
   "outputFileName": "wwwroot/css/site.min.css",
   "inputFiles": [
    "bower_components/bootstrap/dist/css/bootstrap.min.css",
    "content/site.css"
   ]
  },
  {
   "outputFileName": "wwwroot/js/site.min.js",
   "inputFiles": [
    "bower_components/jquery/dist/jquery.min.js",
    "bower_components/jquery-validation/dist/jquery.validate.min.js",
    "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",
    "bower_components/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
    "bower_components/bootstrap/dist/js/bootstrap.min.js"
   ],
   "minify": {
    "enabled": true,
    "renameLocals": true
   },
   "sourceMap": false
  }
]
در اینجا فایل‌های css و اسکریپت مورد نیاز برنامه، به ترتیب اضافه شده و یکی خواهند شد. خروجی نهایی آن‌ها به شکل زیر در صفحات وب مورد استفاده قرار می‌گیرند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/css/site.min.css" rel="stylesheet" />
</head>
<body>
    <div>
        <div>
                @RenderBody()
        </div>
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا تنها دو فایل نهایی این عملیات، یعنی css/site.min.css و js/site.min.js به صفحه الحاق شده‌اند که حاوی تمام پیشنیازهای اسکریپتی و شیوه‌نامه‌های برنامه هستند و در این حالت دیگر نیاز به افزودن آن‌ها به دیگر صفحات سایت نیست.


استفاده از معادل‌های واقعی Unobtrusive Ajax در ASP.NET Core 1.0

واقعیت این است که HTML Helper ای‌جکسی حذف شده‌ی از ASP.NET Core 1.0، کاری بجز افزودن ویژگی‌های data-ajax را که توسط افزونه‌ی jquery.unobtrusive-ajax.min.js پردازش می‌شوند، انجام نمی‌دهد و این افزونه مستقل است از مباحث سمت سرور و به نگارش خاصی از ASP.NET گره نخورده است. بنابراین در اینجا تنها کاری را که باید انجام داد، استفاده از همان ویژگی‌های اصلی است که این افزونه قادر به شناسایی آن‌ها است.
خلاصه‌ی آن‌ها را جهت انتقال کدهای قدیمی و یا تهیه‌ی کدهای جدید، در جدول ذیل می‌توانید مشاهده کنید:

 HTML attribute   AjaxOptions 
 data-ajax-confirm   Confirm 
 data-ajax-method   HttpMethod 
 data-ajax-mode   InsertionMode 
 data-ajax-loading-duration   LoadingElementDuration 
 data-ajax-loading   LoadingElementId 
 data-ajax-begin   OnBegin 
 data-ajax-complete   OnComplete 
 data-ajax-failure   OnFailure 
 data-ajax-success   OnSuccess 
 data-ajax-update   UpdateTargetId 
 data-ajax-url   Url 
   
در ASP.NET Core 1.0، به علت حذف متدهای کمکی Ajax دیگر خبری از AjaxOptions نیست. اما اگر علاقمند به انتقال کدهای قدیمی به ASP.NET Core 1.0 هستید، معادل‌های اصلی این پارامترها را می‌توانید در ستون HTML attribute مشاهده کنید.

چند نکته:
- اگر قصد استفاده‌ی از این ویژگی‌ها را دارید، باید ویژگی "data-ajax="true را نیز حتما قید کنید تا سیستم Unobtrusive Ajax فعال شود.
- ویژگی data-ajax-mode تنها با ذکر data-ajax-update (و یا همان UpdateTargetId پیشین) معنا پیدا می‌کند.
- ویژگی data-ajax-loading-duration نیاز به ذکر data-ajax-loading (و یا همان LoadingElementId پیشین) را دارد.
- ویژگی data-ajax-mode مقادیر before، after و replace-with را می‌پذیرد. اگر قید نشود، کل المان با data دریافتی جایگزین می‌شود.
- سه callback قابل تعریف data-ajax-complete، data-ajax-failure و data-ajax-success، یک چنین پارامترهایی را از سمت سرور در اختیار کلاینت قرار می‌دهند:

parameters  
 Callback  
 xhr, status   data-ajax-complete 
 data, status, xhr   data-ajax-success 
 xhr, status, error   data-ajax-failure 

برای مثال می‌توان ویژگی data-ajax-success را به نحو ذیل در سمت کلاینت مقدار دهی کرد:
 data-ajax-success = "myJsMethod"
این متد جاوا اسکریپتی یک چنین امضایی را دارد:
  function myJsMethod(data, status, xhr) {
}
در این حالت در سمت سرور، پارامتر data در یک اکشن متد، به صورت ذیل مقدار دهی می‌شود:
 return Json(new { param1 = 1, param2 = 2, ... });
و در سمت کلاینت در متد myJsMethod این پارامترها را به صورت data.param1 می‌توان دریافت کرد.


مثال‌هایی از افزودن ویژگی‌های data-ajax به المان‌های مختلف

 در حالت استفاده از Form Tag Helpers که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بررسی شدند، یک فرم ای‌جکسی، چنین تعاریفی را پیدا خواهد کرد:
با این ViewModel فرضی
using System.ComponentModel.DataAnnotations;
 
namespace Core1RtmEmptyTest.ViewModels.Account
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}
که در View متناظر Ajax ایی ذیل استفاده شده‌است:
@using Core1RtmEmptyTest.ViewModels.Account
@model RegisterViewModel
@{
}
 
<form method="post"
      asp-controller="TestAjax"
      asp-action="Index"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      class="form-horizontal"
      role="form"
      data-ajax="true"
      data-ajax-loading="#Progress"
      data-ajax-success="myJsMethod">
 
    <input asp-for="Email" class="form-control" />
    <span asp-validation-for="Email" class="text-danger"></span>
 
    <button type="submit">ارسال</button>
 
    <div id="Progress" style="display: none">
        <img src="images/loading.gif" alt="loading..." />
    </div>
</form>
 
@section scripts{
    <script type="text/javascript">
        function myJsMethod(data, status, xhr) {
            alert(data.param1);
        }
    </script>
}
در اینجا تمام تعاریف مانند قبل است؛ تنها سه ویژگی data-ajax جهت فعال سازی jquery-ajax-unobtrusive به فرم اضافه شده‌اند. همچنین یک callback دریافت پیام موفقیت آمیز بودن عملیات Ajax ایی نیز تعریف شده‌است.

این View از کنترلر ذیل استفاده می‌کند:
using Core1RtmEmptyTest.ViewModels.Account;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class TestAjaxController : Controller
    {
 
        public IActionResult Index()
        {
            return View();
        }
 
        [HttpPost]
        public IActionResult Index([FromForm]RegisterViewModel vm)
        {
            var ajax = isAjax();
            if (ajax)
            {
                // it's an ajax post
            }
 
 
            if (ModelState.IsValid)
            {
                //todo: save data
 
                return Json(new { param1 = 1, param2 = 2 });
            }
            return View();
        }
 
        private bool isAjax()
        {
            return Request?.Headers != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest";
        }
    }
}
به ASP.NET Core 1.0، متد کمکی IsAjax اضافه نشده‌است؛ اما تعریف آن‌را در این کنترلر مشاهده می‌کنید. در مورد قید FromForm در ادامه توضیح داده خواهد شد (هرچند در این مورد خاص، حالت پیش فرض است و الزامی به قید آن نیست).

و یا Action Link ای‌جکسی نیز به صورت خلاصه به این نحو قابل تعریف است:
<div id="EmployeeInfo">
<a 
 asp-controller="MyController" asp-action="MyAction"
 data-ajax="true" 
 data-ajax-loading="#Progress" 
 data-ajax-method="POST" 
 data-ajax-mode="replace" 
 data-ajax-update="#EmployeeInfo">
 Get Employee-1 info
</a>

  <div id="Progress" style="display: none">
    <img src="images/loading.gif" alt="loading..."  />
  </div>
</div>


نکته‌ای در مورد اکشن متدهای ای‌جکسی در ASP.NET Core 1.0

همانطور که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API»، قسمت «تغییرات Model binding پیش فرض، برای پشتیبانی از ASP.NET MVC و ASP.NET Web API» نیز ذکر شد:
public IActionResult Index([FromBody] MyViewModel vm)
{
   return View();
}
ذکر ویژگی FromBody در اینجا الزامی است. از این جهت که اطلاعات با فرمت JSON، از قسمت body درخواست استخراج و به MyViewModel بایند خواهند شد (در حالت dataType: json). و اگر dataType : application/x-www-form-urlencoded; charset=utf-8 بود (مانند حالت پیش فرض Unobtrusive Ajax)، باید از ویژگی FromForm استفاده شود. در غیر اینصورت در سمت سرور نال دریافت خواهیم کرد.