به دلیل اینکه با مباحث مهمی روبرو هستیم، سعی کردم یه منبع پیدا کنم و راجع این مباحث یه مرجع کامل تر داشته باشیم. توجه کنید که ترتیب مباحث از ابتدای صفحه تا انتهای صفحه ست. اول callback رو بفمید و سپس promise و در آخر async و await .
جاوا اسکریپت به صورت پیشفرض یک زبان برنامهنویسی همگام و تک نخی است. این بدان معنی است که کد نمیتواند نخهای جدیدی ایجاد کرده و به صورت موازی اجرا شود. در این مقاله به بررسی روشهای مختلف برنامه نویسی ناهمگام در محیط Node.js میپردازیم.
ناهمگامی در زبانهای برنامهنویسی
رایانهها اساساً دارای طراحی ناهمگام هستند. منظور از ناهمگامی این است که کارهای مختلف میتوانند در گردش برنامه اصلی مستقل از هم اتفاق بیفتند. در رایانههای امروز هر برنامهای برای مدت زمان معینی اجرا میشود و سپس اجرای آن متوقف میشود تا برنامه دیگری بتواند به اجرای خود ادامه بدهد. این کار در چنان چرخه سریعی اتفاق میافتد که کاربر هرگز متوجه نمیشود و ما فکر میکنیم رایانه ما میتواند به طور همزمان برنامههای زیادی را اجرا کند، اما این یک توهم است (البته قضیه در پردازندههای چندهستهای متفاوت است).
برنامهها به صورت داخلی از «وقفه» (interrupt) استفاده میکنند. وقفه یک سیگنال است که به سمت پردازنده ارسال میشود تا توجه سیستم را کسب کند. پرداختن به جزییات این موضوع خارج از موضوع این مقاله است؛ اما باید به خاطر داشته باشید که ناهمگامی در برنامهها امری معمول است و به طور نرمال برنامهها، اجرای خود را متوقف میکنند تا زمانی که پردازنده به آنها وقتی اختصاص بدهد. زمانی که یک برنامه منتظر پاسخی از سوی شبکه است، نمیتواند پردازنده را معطل خود نگه دارد تا درخواست پایان یابد.
به طور معمول زبانهای برنامهنویسی همگام هستند. برخی از آنها نیز در حد زبان یا کتابخانههای جانبی، روشهایی برای مدیریت ناهمگامی ارائه میکنند. زبانهای C ،Java ،C# ،PHP ،Go ،Ruby ،Swift و Python همگی به صورت پیشفرض همگام هستند. برخی از این زبانها با استفاده از نخها به صورت ناهمگام عمل میکنند و میتوانند یک پردازش جدید ایجاد کنند.
جاوا اسکریپت
زبان برنامهنویسی جاوا اسکریپت نیز به صورت پیشفرض همگام و تک نخی است. این بدان معنی است که کد نمیتواند نخهای جدید ایجاد کرده و به صورت موازی اجرا شود. در این حالت، خطوط مختلف کد یکی پس از دیگری و به ترتیب اجرا میشوند. برای نمونه:
const a = 1const b = 2const c = a * bconsole.log(c)doSomething()
اما میدانیم که جاوا اسکریپت درون مرورگر تولد یافته است و وظیفه اصلی آن در ابتدا پاسخ دادن به واکنشهای کاربر مانند onClick ،onMouseOver ،onChange ،onSubmit و غیره بوده است. چنین فرایندی در یک مدل برنامهنویسی همگام چگونه ممکن است؟
پاسخ در محیط برنامهنویسی این زبان است. مرورگر روشی برای ارائه یک مجموعه از API-ها دارد که میتوانند این نوع کارکرد را مدیریت کنند. در سالهای اخیر Node.js یک محیط غیر مسدودکننده I/O معرفی کرده است که این مفهوم را به دسترسی فایلها، فراخوانیهای شبکه و موارد مشابه گسترش داده است.
Callback-ها
ما هرگز نمیدانیم که یک کاربر چه زمانی روی یک دکمه کلیک خواهد کرد، بنابراین باید یک «دستگیره رویداد» (event handler) برای رویداد کلیک تعریف کنیم.
این دستگیره رویداد تابعی قبول میکند که هنگام اتفاق افتادن رویداد فراخوانی میشود:
document.getElementById('button').addEventListener('click'، () => {//item clicked})
این همان callback مشهور است. در واقع callback تابع سادهای است که به صورت یک مقدار به تابع دیگر ارسال میشوند و تنها زمانی اجرا خواهد شد که رویدادی اتفاق بیفتد. ما این کار را به این دلیل میتوانیم اجرا کنیم که جاوا اسکریپت تابعهای درجه نخست دارد که میتوانند به متغیرها انتساب یافته و به تابعهای دیگر (که تابعهای درجه بالا نامیده میشوند) ارسال شوند.
رویه معمول این است که کد کلاینت در یک شنونده رویداد load در شیء window قرار میگیرد و تابع callback را در زمان آماده شدن صفحه اجرا میکند:
window.addEventListener('load'، () => {//window loaded //do what you want}
Callback-ها در همه جا استفاده میشوند و اختصاص به رویدادهای DOM ندارند. یک مثال عمومی استفاده از تایمر است:
setTimeout(() => {// runs after 2 seconds}، 2000
درخواستهای XHR نیز یک callback میپذیرند. در مثال زیر یک تابع به مشخصهای که در زمان رخداد اتفاق خاص فراخوانی میشود، انتساب خواهد یافت:
const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => { if (xhr.readyState === 4) { xhr.status === 200 ? console.log(xhr.responseText) : console.error('error') } } xhr.open('GET', 'https://yoursite.com') xhr.send()
مدیریت خطا در Callback-ها
یکی از رایجترین راهبردها برای مدیریت خطا در callback-ها کاری است که Node.js انجام میدهد. پارامتر اول در هر تابع callback شیء خطا است که به نام callback-های error-first شناخته میشوند.
اگر خطایی وجود نداشته باشد، این شیء null خواهد بود. اگر خطایی رخ داده باشد این پارامتر نوعی توضیح در مورد خطا و اطلاعات مرتبط ارائه میکند.
fs.readFile('/file.json'، (err، data) => { if (err!== null) {//handle error console.log(err) return }
مشکل Callback-ها
به جای استفاده از کال بک میتوانیم کد خود را با پرامیس بنویسیم و یا کدهایی که به صورت کال بک نوشته شده تبدیل به پرامیس کنیم تا بتوانیم در آینده از مزایای async/await هم استفاده کنیم
Callback-ها در استفادههای ساده بسیار عالی هستند. اما هر Callback یک سطح به سلسلهمراتب تودرتو اضافه میکند. زمانی که Callback-های زیادی وجود داشته باشند، کد به سرعت پیچیده میشود:
window.addEventListener('load'،() => { document.getElementById('button').addEventListener('click'،() => { setTimeout(() => { items.forEach(item => { //your code here }) }، 2000) }) })
این کد صرفاً 4 سطح ساده دارد، اما در موارد زیادی سطوح بسیار بیشتری از کد تودرتو را مشاهده میکنیم که جالب نیست. برای حل این مشکل چه باید کرد:
جایگزینهای Callback-ها
از ES6 به بعد جاوا اسکریپت چند قابلیت معرفی کرده است که به کدنویسی ناهمگام بدون استفاده از Callback-ها کمک میکند:
- Promises (ES6)
- Async/Await (ES8)
Promise-ها
Promise-ها یک روش برای اجرای کد ناهمگام در جاوا اسکریپت محسوب میشوند و با استفاده از آنها دیگر لازم نیست نگران وجود Callback-های زیاد در کد باشیم.
مقدمهای بر Promise-ها
یک Promise عموماً به صورت واسطی برای یک مقدار تعریف میشود که در زمانی در آینده موجود خواهد شد. با این که سالهاست Promise-ها معرفی شدهاند اما صرفاً در ES2015 استاندارد و معرفی شدند و هم اکنون در ES2017 با معرفی تابعهای async منسوخ گشتهاند. تابعهای async از API مربوط به Promise ها به عنوان بلوکهای سازنده خود استفاده میکند و از این رو درک آنها حتی در صورتی که بخواهید در کد خود از تابعهای async به جای promise استفاده کنید ضروری خواهد بود.
طرز کار Promise-ها به طور خلاصه
زمانی که یک Promise فراخوانی میشود در «حالت انتظار» (pending state) کار خود را آغاز میکند. این بدان معنی است که تابع فراخواننده به اجرای خود ادامه میدهد در حالی که منتظر Promise است تا پردازشهای خودش را اجرا کند و بازخوردی به تابع فراخواننده بدهد.
در این زمان تابع فراخواننده منتظر آن میماند تا Promise را در «حالت حل شده» (resolved state) یا در «حالت رد شده» (rejected state) بازگشت دهد، اما چنان که میدانید جاوا اسکریپت همگام است و از این رو در زمانی که Promise کار نمیکند، تابع همچنان به اجرای خود ادامه میدهد.
کدام API جاوا اسکریپت از Promise-ها استفاده میکند؟
علاوه بر کد کاربر و کد کتابخانههای مختلف، Promise-ها از سوی API-های مدرن استاندارد وب مانند لیست زیر نیز مورد استفاده قرار میگیرند:
- Battery API
- Fetch API
- Service Workers
این که در جاوا اسکریپت مدرن از Promise-ها استفاده نکنید امر بسیار نامحتملی است، بنابراین در ادامه به بررسی دقیقتر آنها میپردازیم.
ایجاد یک Promise
API مربوط به Promise یک سازنده Promise عرضه میکند که با استفاده از ()new Promise مقداردهی اولیه میشود:
let done = true ---------------------------------------- const isItDoneYet = new Promise((resolve، reject) => { if (done) { const workDone = 'Here is the thing I built' resolve(workDone) } else { const why = 'Still working on something else' reject(why) } })
چنان که میبینید Promise ثابت سراسری done را بررسی میکند و اگر درست باشد، یک Promise حل شده و در غیر این صورت Promise رد شده بازگشت مییابد. ما با استفاده از resolve و reject میتوانیم یک مقدار را بازگشت دهیم. در حالت فوق ما صرفاً یک رشته بازگشت میدهیم اما میتوان یک شیء را نیز بازگشت داد.
مصرف کردن یک Promise
در بخش قبلی، با روش ایجاد شدن یک Promise آشنا شدیم. اکنون به بررسی شیوه مصرف یا استفاده شدن یک Promise میپردازیم:
const isItDoneYet = new Promise(//...) ----------------------------------------------------------------- const checkIfItsDone = () => { isItDoneYet.then((ok) => { console.log(ok) }).catch((err) => { console.error(err) }) }
جاوا اسکریپت به صورت پیشفرض یک زبان برنامهنویسی همگام و تک نخی است. این بدان معنی است که کد نمیتواند نخهای جدیدی ایجاد کرده و به صورت موازی اجرا شود. در این مقاله به بررسی روشهای مختلف برنامه نویسی ناهمگام در محیط Node.js میپردازیم.
ناهمگامی در زبانهای برنامهنویسی
رایانهها اساساً دارای طراحی ناهمگام هستند. منظور از ناهمگامی این است که کارهای مختلف میتوانند در گردش برنامه اصلی مستقل از هم اتفاق بیفتند. در رایانههای امروز هر برنامهای برای مدت زمان معینی اجرا میشود و سپس اجرای آن متوقف میشود تا برنامه دیگری بتواند به اجرای خود ادامه بدهد. این کار در چنان چرخه سریعی اتفاق میافتد که کاربر هرگز متوجه نمیشود و ما فکر میکنیم رایانه ما میتواند به طور همزمان برنامههای زیادی را اجرا کند، اما این یک توهم است (البته قضیه در پردازندههای چندهستهای متفاوت است).
برنامهها به صورت داخلی از «وقفه» (interrupt) استفاده میکنند. وقفه یک سیگنال است که به سمت پردازنده ارسال میشود تا توجه سیستم را کسب کند. پرداختن به جزییات این موضوع خارج از موضوع این مقاله است؛ اما باید به خاطر داشته باشید که ناهمگامی در برنامهها امری معمول است و به طور نرمال برنامهها، اجرای خود را متوقف میکنند تا زمانی که پردازنده به آنها وقتی اختصاص بدهد. زمانی که یک برنامه منتظر پاسخی از سوی شبکه است، نمیتواند پردازنده را معطل خود نگه دارد تا درخواست پایان یابد.
به طور معمول زبانهای برنامهنویسی همگام هستند. برخی از آنها نیز در حد زبان یا کتابخانههای جانبی، روشهایی برای مدیریت ناهمگامی ارائه میکنند. زبانهای C ،Java ،C# ،PHP ،Go ،Ruby ،Swift و Python همگی به صورت پیشفرض همگام هستند. برخی از این زبانها با استفاده از نخها به صورت ناهمگام عمل میکنند و میتوانند یک پردازش جدید ایجاد کنند.
جاوا اسکریپت
زبان برنامهنویسی جاوا اسکریپت نیز به صورت پیشفرض همگام و تک نخی است. این بدان معنی است که کد نمیتواند نخهای جدید ایجاد کرده و به صورت موازی اجرا شود. در این حالت، خطوط مختلف کد یکی پس از دیگری و به ترتیب اجرا میشوند. برای نمونه:
const a = 1const b = 2const c = a * bconsole.log(c)doSomething()
اما میدانیم که جاوا اسکریپت درون مرورگر تولد یافته است و وظیفه اصلی آن در ابتدا پاسخ دادن به واکنشهای کاربر مانند onClick ،onMouseOver ،onChange ،onSubmit و غیره بوده است. چنین فرایندی در یک مدل برنامهنویسی همگام چگونه ممکن است؟
پاسخ در محیط برنامهنویسی این زبان است. مرورگر روشی برای ارائه یک مجموعه از API-ها دارد که میتوانند این نوع کارکرد را مدیریت کنند. در سالهای اخیر Node.js یک محیط غیر مسدودکننده I/O معرفی کرده است که این مفهوم را به دسترسی فایلها، فراخوانیهای شبکه و موارد مشابه گسترش داده است.
Callback-ها
ما هرگز نمیدانیم که یک کاربر چه زمانی روی یک دکمه کلیک خواهد کرد، بنابراین باید یک «دستگیره رویداد» (event handler) برای رویداد کلیک تعریف کنیم.
این دستگیره رویداد تابعی قبول میکند که هنگام اتفاق افتادن رویداد فراخوانی میشود:
document.getElementById('button').addEventListener('click'، () => {//item clicked})
این همان callback مشهور است. در واقع callback تابع سادهای است که به صورت یک مقدار به تابع دیگر ارسال میشوند و تنها زمانی اجرا خواهد شد که رویدادی اتفاق بیفتد. ما این کار را به این دلیل میتوانیم اجرا کنیم که جاوا اسکریپت تابعهای درجه نخست دارد که میتوانند به متغیرها انتساب یافته و به تابعهای دیگر (که تابعهای درجه بالا نامیده میشوند) ارسال شوند.
رویه معمول این است که کد کلاینت در یک شنونده رویداد load در شیء window قرار میگیرد و تابع callback را در زمان آماده شدن صفحه اجرا میکند:
window.addEventListener('load'، () => {//window loaded //do what you want}
Callback-ها در همه جا استفاده میشوند و اختصاص به رویدادهای DOM ندارند. یک مثال عمومی استفاده از تایمر است:
setTimeout(() => {// runs after 2 seconds}، 2000
درخواستهای XHR نیز یک callback میپذیرند. در مثال زیر یک تابع به مشخصهای که در زمان رخداد اتفاق خاص فراخوانی میشود، انتساب خواهد یافت:
const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => { if (xhr.readyState === 4) { xhr.status === 200 ? console.log(xhr.responseText) : console.error('error') } } xhr.open('GET', 'https://yoursite.com') xhr.send()
مدیریت خطا در Callback-ها
یکی از رایجترین راهبردها برای مدیریت خطا در callback-ها کاری است که Node.js انجام میدهد. پارامتر اول در هر تابع callback شیء خطا است که به نام callback-های error-first شناخته میشوند.
اگر خطایی وجود نداشته باشد، این شیء null خواهد بود. اگر خطایی رخ داده باشد این پارامتر نوعی توضیح در مورد خطا و اطلاعات مرتبط ارائه میکند.
fs.readFile('/file.json'، (err، data) => { if (err!== null) {//handle error console.log(err) return }
مشکل Callback-ها
Callback-ها در استفادههای ساده بسیار عالی هستند. اما هر Callback یک سطح به سلسلهمراتب تودرتو اضافه میکند. زمانی که Callback-های زیادی وجود داشته باشند، کد به سرعت پیچیده میشود:
window.addEventListener('load'،() => { document.getElementById('button').addEventListener('click'،() => { setTimeout(() => { items.forEach(item => { //your code here }) }، 2000) }) })
این کد صرفاً 4 سطح ساده دارد، اما در موارد زیادی سطوح بسیار بیشتری از کد تودرتو را مشاهده میکنیم که جالب نیست. برای حل این مشکل چه باید کرد؟
جایگزینهای Callback-ها
از ES6 به بعد جاوا اسکریپت چند قابلیت معرفی کرده است که به کدنویسی ناهمگام بدون استفاده از Callback-ها کمک میکند:
- Promises (ES6)
- Async/Await (ES8)
Promise-ها
Promise-ها یک روش برای اجرای کد ناهمگام در جاوا اسکریپت محسوب میشوند و با استفاده از آنها دیگر لازم نیست نگران وجود Callback-های زیاد در کد باشیم.
مقدمهای بر Promise-ها
یک Promise عموماً به صورت واسطی برای یک مقدار تعریف میشود که در زمانی در آینده موجود خواهد شد. با این که سالهاست Promise-ها معرفی شدهاند اما صرفاً در ES2015 استاندارد و معرفی شدند و هم اکنون در ES2017 با معرفی تابعهای async منسوخ گشتهاند. تابعهای async از API مربوط به Promise ها به عنوان بلوکهای سازنده خود استفاده میکند و از این رو درک آنها حتی در صورتی که بخواهید در کد خود از تابعهای async به جای promise استفاده کنید ضروری خواهد بود.
طرز کار Promise-ها به طور خلاصه
زمانی که یک Promise فراخوانی میشود در «حالت انتظار» (pending state) کار خود را آغاز میکند. این بدان معنی است که تابع فراخواننده به اجرای خود ادامه میدهد در حالی که منتظر Promise است تا پردازشهای خودش را اجرا کند و بازخوردی به تابع فراخواننده بدهد.
در این زمان تابع فراخواننده منتظر آن میماند تا Promise را در «حالت حل شده» (resolved state) یا در «حالت رد شده» (rejected state) بازگشت دهد، اما چنان که میدانید جاوا اسکریپت همگام است و از این رو در زمانی که Promise کار نمیکند، تابع همچنان به اجرای خود ادامه میدهد.
کدام API جاوا اسکریپت از Promise-ها استفاده میکند؟
علاوه بر کد کاربر و کد کتابخانههای مختلف، Promise-ها از سوی API-های مدرن استاندارد وب مانند لیست زیر نیز مورد استفاده قرار میگیرند:
- Battery API
- Fetch API
- Service Workers
این که در جاوا اسکریپت مدرن از Promise-ها استفاده نکنید امر بسیار نامحتملی است، بنابراین در ادامه به بررسی دقیقتر آنها میپردازیم.
ایجاد یک Promise
API مربوط به Promise یک سازنده Promise عرضه میکند که با استفاده از ()new Promise مقداردهی اولیه میشود:
let done = true ---------------------------------------- const isItDoneYet = new Promise((resolve، reject) => { if (done) { const workDone = 'Here is the thing I built' resolve(workDone) } else { const why = 'Still working on something else' reject(why) } })
چنان که میبینید Promise ثابت سراسری done را بررسی میکند و اگر درست باشد، یک Promise حل شده و در غیر این صورت Promise رد شده بازگشت مییابد. ما با استفاده از resolve و reject میتوانیم یک مقدار را بازگشت دهیم. در حالت فوق ما صرفاً یک رشته بازگشت میدهیم اما میتوان یک شیء را نیز بازگشت داد.
مصرف کردن یک Promise
در بخش قبلی، با روش ایجاد شدن یک Promise آشنا شدیم. اکنون به بررسی شیوه مصرف یا استفاده شدن یک Promise میپردازیم:
const isItDoneYet = new Promise(//...) ----------------------------------------------------------------- const checkIfItsDone = () => { isItDoneYet.then((ok) => { console.log(ok) }).catch((err) => { console.error(err) }) }
اجرای ()checkIfItsDone موجب اجرا شدن یک Promise به نام ()isItDoneYet میشود که منتظر حل شدن با استفاده از Callback-ی به نام then باقی میماند و اگر خطایی رخ دهد آن را در callback با نام catch مدیریت میکند.
زنجیرهسازی Promise-ها
هر Promise میتواند به Promise دیگری بازگشت یابد و یک زنجیره از Promise-ها تشکیل دهد. مثالی عالی از زنجیرهسازی Promise-ها در API با نام Fetch مشاهده میشود که یک لایه فوقانی روی API مربوط به XMLHttpRequest است و میتوان از آن برای ایجاد یک منبع و صفبندی یک زنجیره از Promise-ها برای اجرا در زمان واکشی شدن منبع استفاده کرد.
API با نام Fetch یک سازوکار مبتنی بر Promise است به طوری که فراخوانی ()fetch معادل تعریف کردن یک Promise با استفاده از ()new Promise است.
نمونهای از زنجیرهسازی Promise-ها
const status = (response) => { if (response.status >= 200 && response.status < 300) { return Promise.resolve(response) } return Promise.reject(new Error(response.statusText)) }
const json = (response) => response.json()
fetch('/todos.json').then(status).then(json).then((data) => { console.log('Request succeeded with JSON response'، data) }).catch((error) => { console.log('Request failed'، error) })
در این مثال، ()fetch را فراخوانی میکنیم تا فهرستی از آیتمهای TODO را از فایل todos.json که در ریشه دامنه قرار دارد به دست آوریم و بنابراین یک زنجیره از Promise-ها ایجاد کردهایم.
اجرای ()fetch موجب بازگشت یک response میشود که مشخصههای زیادی دارد، اما دو مورد از آنها برای ما مهم هستند:
- Status – یک مقدار عددی است که کد وضعیت HTTP را نشان میدهد.
- statusText – یک پیام وضعیت است که در صورت موفق بودن درخواست، مقدار آن OK خواهد بود.
response یک متد ()json نیز دارد که یک Promise بازگشت میدهد و در آن به بررسی محتوای متنی پردازش شده و تبدیل آن به JSON میپردازد. با توجه به Promise-های فوق روند رویدادها به این صورت است که Promise اول در زنجیره، تابعی است که ما تعریف کردهایم و آن را ()status مینامیم. این Promise وضعیت پاسخ را بررسی میکند و در صورتی که پاسخ موفق (کد بین 200 تا 299) نباشد، Promise را رد میکند.
این عملیات موجب میشود که زنجیره Promise همه Promise-های بعدی لیست شده را رد کند و مستقیماً به گزاره ()catch در انتهای کد برسد و متن Request failed را به همراه پیام خطا لاگ کند. اما اگر Promise اول موفق باشد، تابع ()json را که تعریف کردهایم، فراخوانی میکند. از آنجا که Promise قبلی در صورت موفقیت، شیء response را بازگشت داده است، میتوانیم آن را به عنوان ورودی به Promise دوم بدهیم.
در این حالت، دادههای JSON پردازش شده را بازگشت میدهیم تا Promise سوم مستقیماً JSON را دریافت کند:
.then((data) => { console.log('Request succeeded with JSON response'، data)})
در نهایت آن را در کنسول لاگ میکنیم.
مدیریت خطاها
در مثال بخش قبلی، یک catch داشتیم که به زنجیره Promise-ها الصاق میشد. هر زمان که جزئی از زنجیره Promise-ها ناموفق باشد و خطایی رخ دهد یا یک Promise رد شود، کنترل به گزاره ()catch در انتهای زنجیره میرسد.
new Promise((resolve، reject) => { throw new Error('Error') }).catch((err) => { console.error(err) })
یا
new Promise((resolve، reject) => { reject('Error') }).catch((err) => { console.error(err) })
آبشار سازی خطاها
اگر درون ()catch خطایی رخ دهد، میتوان یک ()catch دیگر برای مدیریت آن الصاق کرد و این روند همین طور تا انتها ادامه مییابد:
new Promise((resolve، reject) => { throw new Error('Error') }).catch((err) => { throw new Error('Error') }).catch((err) => { console.error(err) })
هماهنگسازی Promise-ها
در این بخش به روش هماهنگ کردن چند Promise میپردازیم.
()Promise.all
اگر لازم است که Promise-های مختلف را با یکدیگر هماهنگ کنید، میتوانید از متد ()Promise.all برای تعریف کردن لیستی از Promise-ها استفاده کنید و زمانی که همه آنها حل شدند، تابع دیگری را اجرا کنید.
مثالی از آن به صورت زیر است:
const f1 = fetch('/something.json') const f2 = fetch('/something2.json') Promise.all([f1، f2]).then((res) => { console.log('Array of results'، res) }).catch((err) => { console.error(err) })
ساختار destructuring assignment در ES2015 نیز اجازه اجرای چنین کاری را میدهد:
Promise.all([f1، f2]).then(([res1، res2]) => { console.log('Results'، res1، res2) })
البته شما محدود به استفاده از fetch نیستید و میتوانید از هر Promise دیگری نیز استفاده کنید.
()Promise.race
این متد زمانی اجرا میشود که اولین Promise که به آن ارسال کردهاید حل شود و callback الصاق شده به آن را یک بار به همراه نتیجه نخستین Promise حل شده اجرا میکند. مثالی از آن به صورت زیر است:
const first = new Promise((resolve، reject) => { setTimeout(resolve، 500، 'first') }) const second = new Promise((resolve، reject) => { setTimeout(resolve، 100، 'second') }) Promise.race([first، second]).then((result) => { console.log(result) // second}
یک خطای رایج
اگر با خطای زیر در کنسول مواجه شدید:
Uncaught TypeError: undefined is not a promise
ابتدا باید اطمینان حاصل کنید که به جای ()Promise از ()new Promise استفاده کردهاید.
Async و Await
جدیدترین رویکرد به تابعهای ناهمگام در جاوا اسکریپت Async و Await است. جاوا اسکریپت در طی مدت بسیار کوتاهی (ES2015) استفاده از Callback را کنار گذاشت و به بهرهگیری از Promise-ها روی آورد. از نسخه ES2017 نیز کدهای ناهمگام در جاوا اسکریپت با استفاده از ساختار async/await سادهتر شدهاند.
تابعهای async ترکیبی از Promise-ها و generatos-ها هستند و اساساً لایه بالاتری از تجرید نسبت به Promise محسوب میشوند. در اینجا باید یک بار دیگر تکرار کنیم که async/await بر مبنای Promise-ها ساخته شدهاند.
Async/Await چرا معرفی شد؟
تابعهای Async/Await باعث کاهش حجم کد مورد نیاز برای نوشتن Promise-ها میشوند و محدودیت «عدم قطع زنجیره» Promise-های زنجیرهسازی را نیز مرتفع میسازند. زمانی که Promise-ها در نسخه ES2015 معرفی شدند، هدف از ارائه آنها حل مشکلی در کدنویسی ناهمگام بود و در این کار نیز موفق بودند، اما در طی دو سال که بین عرضه نسخههای ES2015 و ES2017 وجود داشت مشخص شد که Promise-ها نمیتوانند راهحل نهایی باشند.
Promise-ها برای حل مشکل مشهور جهنم callback-ها عرضه شدند، اما آنها نیز پیچیدگی خاص خود را داشتند و از نظر کاربران دارای ساختار پیچیدهای بودند. Promise-ها با این کارکرد که به ما نشان دادند میتوان از ساختار مناسبتری به این منظور استفاده کرد مفید بودند و لذا اینک زمانی فرا رسیده است که از تابعهای async استفاده کنیم. تابعهای async/await موجب میشوند کد همگام به نظر برسد، اما اساساً ناهمگام است و در پشت صحنه به روشی غیر مسدودکننده عمل میکند.
طرز کار Async/Await چگونه است؟
تابع async یک Promise بازگشت میدهد. به مثال زیر توجه کنید:
const doSomethingAsync = () => { return new Promise((resolve) => { setTimeout(() => resolve('I did something')، 3000) }) }
زمانی که بخواهیم این تابع را فراخوانی کنیم یک await آماده میکنیم و کد فراخوانی کننده متوقف میشود تا این که Promise حل یا رد شود. تنها الزام این است که تابع کلاینت باید به صورت async تعریف شود. به مثال زیر توجه کنید:
const doSomething = async () => { console.log(await doSomethingAsync()) }
یک مثال ساده
در مثال ساده زیر از async/await برای اجرای ناهمگام یک تابع استفاده شده است:
const doSomethingAsync = () => { return new Promise((resolve) => { setTimeout(() => resolve('I did something')، 3000) }) } const doSomething = async () => { console.log(await doSomethingAsync()) } console.log('Before') doSomething() console.log('After')
کد فوق عبارت زیر را در کنسول مرورگر نمایش میدهد:
BeforeAfterI did something //after 3s
Promise کردن همه چیز
الصاق کردن کلیدواژه async به هر تابعی به این معنی است که تابع یک Promise بازگشت خواهد داد. حتی اگر این کار به طور صریح انجام نشده باشد، این کار موجب میشود که تابع به صورت درونی یک Promise بازگشت دهد. به همین دلیل است که کد زیر معتبر است:
const aFunction = async () => { return 'test'}
aFunction().then(alert) // This will alert 'test'
این کد نیز مانند کد فوق است:
const aFunction = async () => { return Promise.resolve('test')}
aFunction().then(alert) // This will alert 'test
خواندن این کد کاملاً آسانتر است
چنان که در مثال فوق میبینیم، کد ما بسیار ساده به نظر میرسد. آن را با کدی که از Promise-های ساده استفاده میکرد و از زنجیرهسازی و تابعهای callback بهره میگرفت مقایسه کنید. این یک مثال بسیار ساده است، مزیت اصلی زمانی بروز مییابد که کد بسیار پیچیدهتر باشد. برای نمونه با استفاده از Promise-ها به صورت زیر میتوان یک منبع JSON را دریافت کرده و آن را تحلیل کرد:
const getFirstUserData = () => { return fetch('/users.json') get users list.then(response => response.json()) parse JSON.then(users => users[0]) pick first user.then(user => fetch(`/users/${user.name}`)) get user data.then(userResponse => response.json()) parse JSON } getFirstUserData()
کد زیر همان کارکرد فوق را با استفاده از await/async اجرا میکند:
const getFirstUserData = async () => { const response = await fetch('/users.json') get users list const users = await response.json() parse JSON const user = users[0] pick first user const userResponse = await fetch(`/users/${user.name}`) get user data const userData = await user.json() parse JSON return userData } getFirstUserData()
سری کردن چند تابع Async
تابعهای Async را میتوان بسیار ساده به هم زنجیر کرد و ساختار آن نسبت به Promise-های ساده بسیار خواناتر است:
const promiseToDoSomething = () => { return new Promise(resolve => { setTimeout(() => resolve('I did something'), 10000) }) } const watchOverSomeoneDoingSomething = async () => { const something = await promiseToDoSomething() return something + ' and I watched' } const watchOverSomeoneWatchingSomeoneDoingSomething = async () => { const something = await watchOverSomeoneDoingSomething() return something + ' and I watched as well' } watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => { console.log(res)})
کد فوق عبارت زیر را نمایش میدهد:
I did something and I watched and I watched as well
دیباگ آسانتر
دیباگ کردن Promise-ها دشوار است زیرا دیباگر روی کد ناهمگام نمیایستد. اما async/await کار دیباگ را بسیار آسانتر ساختهاند، زیرا از نظر کامپایلر آنها کد همگام هستند.
Event Emitter در Node.js
با استفاده از Event Emitter در Node.js میتوان رویدادهای سفارشی ایجاد کرد. اگر با جاوا اسکریپت در مرورگر کار کرده باشید، میدانید که بخش عمده تعامل با کاربر از طریق رویدادهایی مانند کلیکهای ماوس، فشردن کلیدهای کیبورد، واکنش به حرکت ماوس و غیره مدیریت میشود. در سمت بکاند، Node.js کلاس EventEmitter را ارائه کرده است که برای مدیریت رویدادها استفاده میشود. این کلاس را میتوان با استفاده از کد زیر مقداردهی کرد:
const eventEmitter = require('events').EventEmitter()
این شیء متدهای on و emit برخی متدهای دیگر را عرضه میکند.
- emit – برای تحریک کردن یک رویداد استفاده میشود.
- on – برای افزودن تابع callback استفاده میشود که قرار است هنگام تحریک شدن رویداد اجرا شود.
برای نمونه، یک رویداد start ایجاد میکنیم و از آنجا که میخواهیم مثال سادهای باشد، واکنش ما به آن لاگ کردن پیامی در کنسول است:
eventEmitter.on('start'، () => { console.log('started')})
زمانی که رویداد را اجرا کنیم:
eventEmitter.emit('start')
تابع دستگیره رویداد تحریک میشود و لاگ کنسول ایجاد میشود. با استفاده از آرگومانهای اضافی ()emit میتوان آرگومانهایی به دستگیره رویداد ارسال کرد:
eventEmitter.emit('start') eventEmitter.emit('start'، 23)
آرگومانهای چندگانه
eventEmitter.on('start'، (start، end) => { console.log(`started from ${start} to ${end}`)}) eventEmitter.emit('start'، 1، 100)
شیء EventEmitter چند متد دیگر نیز برای تعامل با رویدادها در اختیار ما قرار میدهد که از آن جملهاند:
- ()once – یک شنونده یکبارمصرف اضافه میکند.
- ()removeListener() / off – یک شنونده رویداد را از رویداد حذف میکند.
- ()removeAllListeners – همه شنوندهها را از یک رویداد حذف میکند.
منبع: وبلاگ فرادرس
تصور کنید یک فایل داریم با نام data.json و محتوای درون آن شامل یک آرایه از آبجکت های json است.
هدف ما اینه که میخواهیم علاوه بر اینکه این فایل رو میخونیم و اطلاعاتش رو درون یک متغیر ذخیره میکنیم. یک عضو دیگه هم به آرایه اضافه کنیم و آرایه جدید رو درون فایل data.json بنویسیم.
const fs = require('fs'); let data = fs.readFileSync('data.json'); data = JSON.parse(data); data.push({myName:"Mokhtar"}); fs.writeFileSync('data.json',JSON.stringify(data)); console.log(data);
تصور کنید فایل data.json وجود نداشته باشد و ما برنامه رو اجرا کردیم. برنامه کرش میکنه و خطا میده. در صفحه مرجعی که برای جاواسکریپت تهیه کردم، ساختار try , catch , finally توضیح داده شده که الان به دردمون میخوره و میتونیم کاری کنیم که برنامه به کار خودش ادامه بده و خطا رو در کنسول چاپ کنه.
try{ let data = fs.readFileSync('data.json'); data = JSON.parse(data); data.push({myName:"Mokhtar"}); fs.writeFileSync('data.json',JSON.stringify(data)); console.log(data) }catch(err){ console.log(err) } console.log('program working whitout any crash 1.'); console.log('program working whitout any crash 2.'); console.log('program working whitout any crash 3.'); console.log('program working whitout any crash 4.'); console.log('program working whitout any crash 5.');
فایل data.json رو تغییر نام بدید و برنامه رو اجرا کنید. تمام پیام ها انتهای کدمون لاگ میشن که یعنی برنامه متوقف نشده و داره به کارش تا خط آخر ادامه میده.
فرق اساسی Async با Sync در زمان اجرای دستورات است. در Sync یک درخواست بلافاصله اجرا می شود و بقیه سیستم باید منتظر پایان آن بماند. اما در Async درخواست به سیستم ارسال میشود و سیستم عامل قول میدهد که پایان آن را اعلام کند اما اینکه دقیقا کی انجام میشود معلوم نیست.
استفاده از Async به معنی سریعتر شدن اجرای برنامه نیست. تضمینی وجود ندارد که متدهای Async به صورت موازی در Thread مجزا اجرا شوند.تصمیم گیری آن به عهده ی سیستم است. در واقع Async با برنامه نویسی موازی فرق دارد. در برنامه نویسی موازی سیستم عامل را مجبور می کنید از چند Thread جداگانه برای اجرای عملیات استفاده کند اما در Async این انتخاب به عهده ی سیستم عامل است.
از آنجا که زمان پایان در Async مشخص نیست Thread بعد از ارسال Task به سیستم عامل آزاد میشود و می تواند Request جدیدی را دریافت کند. می توان گفت در حالت Sync منابع سیستم بعد از پایان یک Request آزاد میشود اما در Async بعد از شروع Request آزاد میشود. با این روش صف زودتر پیش میرود و احتمال اینکه درخواستی به علت زمان طولانی شدن زمان انتظار برگشت بخورد کمتر می شود.
در Async کار آهسته و پیوسته پیش میرود و همیشه حرکت رو به جلو وجود دارد. اما در Sync پیشرفت مقطعی است. وقتی ترافیک سرور بالا می رود عدم پیشرفت به خطای Server Time Out منجر میشود. در سرور Sync این حالت تا چند برابر (به صورت توانی) زودتر پیش می آید.
نتیجه
در وب توصیه میشود از متدهای Async استفاده کنید. معمولا پیاده سازی متدهای Async پیچیده تر است و کد نویسی آن زمان بیشتری می برد. اما در این روش منابع سرور به سرعت آزاد میشود و می تواند به Request های بیشتری پاسخ دهد. مخصوصا زمانی که با فایل یا دیتابیس کار می کنید.
منبع : کدهک
منبع : وبسایت سایت آموزی
تعریف نوع سند | <!DOCTYPE> |
تعریف یک سند HTML | <html> |
تعریف اطلاعات مربوط به سند | <head> |
تعریف یک عنوان برای سند | <title> |
تعریف بدنه اصلی سند | <body> |
تعریف تیتر عناوین سرصفحه | <h1> ... to ... <h6> |
تعریف یک پاراگراف | <p> |
درج یک شکست خط (حالت اینتر زدن) | <br> |
تعریف تغییر موضوع یا جدا کردن محتوای صفحه از یکدیگر | <hr> |
درج یادداشت یا همان کامنت | <!-- ... --> |
توضیحات | خواص | تگ |
---|---|---|
کلمه ای که مخفف چند کلمه است | <abbr> | |
درج اطلاعات تماس نویسنده، صاحب سند یا مقاله | <address> | |
ضخیم یا شاخص کردن قسمتی از متن | <strong> | |
جدا کردن بخشی از متن که ممکن است جهتش تغییر کند | <bdi> | |
جهت فعلی متن را نادیده میگیرد و حروف را برعکس میچیند | <bdo> | |
تعریف بخشی از متن که از جای دیگری (منبع دیگری) نقل شده است | cite | <blockquate> |
تعریف عنوان یک اثر | <cite> | |
تکه ای از متن که فونت زبان ماشین (کد کامپیوتر) دارد | <coed> | |
تعریف متنی که از سند حذف شده است. (متن خط خورده) | cite - datetime | <del> |
نمونه تعیین کننده یک اصطلاح را نشان میدهد (فونت متفاوت) | <dfn> | |
تعریف یک متن تاکید شده (فونت متفاوت) | <em> | |
تعریف بخشی از متن با حالت مفهومی مثل آیکن ها | <i> | |
تعریف متنی که در یک سند درج شده است، (زیر خط دار) بر عکس <del> | cite - datetime | <ins> |
تعریف ورودی صفحه کلید با فونت کامپیوتری | <kbd> | |
هایلایت کردن قسمتی از متن | <mark> | |
تعریف یک مقیاس اندازه گیری در یک محدوده مشخص | form - high - low - min - max - optimum - value | <meter> |
تعریف یک متن قالب بندی و پیش فرمت شده | <pre> | |
تعریف پیشرفت یک کار یا پروسه را نشان میدهد | form - high - low - min - max - optimum - value | <progress> |
تعریف یک نقل قول کوتاه | cite | <q> |
حاشیه نویسی روبی | <rp> | |
مربوط به زبانهای آسیای شرقی مثل چین و ژاپن | <rt> | |
حاشیه نویسی روبی | <ruby> | |
متنی را تعریف میکند که گزاره درستی نیست، اما قبلا درست بوده | <s> | |
خروجی نمونه از یک برنامه کامپیوتری با فونت مخصوص | <samp> | |
متن با اندازه کوچک | <small> | |
متن یا کلمه ای که زیر مجموعه متن یا کلمه دیگری است (پائین متن والد) | <sub> | |
متن یا کلمه ای که زیر مجموعه متن یا کلمه دیگری است (بالای متن والد) | <sup> | |
تعریف یک قالب (توسط کلاینت قابل مشاهده نیست) | <template> | |
تعریف یک عنصر برای نمایش زمان | datetime | <time> |
تعریف یک متن زیرخط دار | <u> | |
تعریف متنی که نام یک متغیر را نشان میدهد | <var> | |
تعریف یک شکست خط احتمالی | <wbr> |
توضیح تگ meterr : اگر مقدار از low کمتر باشد و optimum از high بیشتر باشد (رنگ قرمز میشود). اگر مقدار از high بیشتر باشد و optimum از low کمترباشد (رنگ قرمز میشود). بقیه مقادیر با رنگ زرد و سبز نمایش داده میشوند. موارد کاربردی : مثلاً مصرف دیسک و نمرات.
میتوان با طول و عرض و سایه به آن استایل و انیمیشن داد.
توضیحات | خواص | تگ |
---|---|---|
تعریف یک فرم html برای قرار دادن ورودی های کاربر | enctype - method - novalidate - onreset - onsubmit - target - accept_charset action - autocomplete - name | <form> |
تعریف یک کنترل ورودی | alt - autocomplete - autofocus - dirname - disabled - form - formaction height - list max - maxlength - min - multiple - name - onload - pattern - placeholder - readonly - required - size - src - step - type - value - width | <input> |
تعریف یک کنترل ورودی چندخطی | autofocus - dirname - disabled - form - maxlength - name - placeholder - readonly - required wrap - cols - rows - | <textarea> |
تعریف دکمه قابل کلیک | autofocus - disabled - form - formaction - name - type - value | <button> |
تعریف یک لیست افتادنی dropdown | autofocus - disabled - form - multiple - name - required - size | <select> |
تعریف گروهی از گزینه های مرتبط در یک لیست | disabled - label | <optgroup> |
تعریف یک گزینه درون لیست افتادنی | disabled - value - label - selected | <option> |
تعریف یک برچسب یا عنوان برای عناصر فرم | form - for | <label> |
گروه بندی عناصر مربوط به هم درون یک فرم | disabled - name | <fieldset> |
تعریف یک عنوان برای عنصر <fieldset> | <legend> | |
لیستی از گزینه های از پیش تعریف شده برای کنترل ورودی را مشخص میکند | <datalist> | |
تعریف نتیجه یک محاسبه | form - for | <output> |
تعریف یک فریم درون خطی یا inline در صفحه | height - name - onload - src - width - sandbox - srcdoc | <iframe> |
توضیحات | خواص | تگ |
---|---|---|
تعریف یک تصویر | alt - height - onload - src - width - ismap - usemap | <img> |
تعریف یک نقشه با مختصات تصویر در سمت کلاینت | name | <map> |
تعریف یک محدوده درون نقشه تصویر | alt - coords - download - href - hreflang - media - rel - shape - target | <area> |
قابل استفاده برای کشیدن گرافیک دلخواه در جاوااسکریپت | height - width | <canvas> |
تعریف یک عنوان برای عنصر <figure> در بالا و پائین آن | <figcaption> | |
محتوای مربوط به خود را مشخص میکند | <figure> | |
یک ظرف برای منایع تصویری متعدد تعریف میکند | <picture> | |
تعریف یک ظرف برای گرافیک یا تصویر svg | <svg> |
توضیحات | خواص | تگ |
---|---|---|
تعریف یک محتوای صوتی | رجوع به پائین جدول | <audio> |
تعریف منبع محتوای چندرسانه ای برای عناصر رسانه مثل audio,video,picture | media - sizes - src - srcset - type | <source> |
تعریف متن آهنگ برای audio, video | default - kind - label - oncuechange - srclang | <track> |
تعریف یک ویدئو یا فیلم | رجوع به پائین جدول | <video> |
خواص مربوط به تگ های audio و video
onvolumechange
onwaiting
preload
src
autoplay
controls
loop
muted
onabort
oncanplay
oncanplaythrough
ondurationchange
onemptied
onended
onerror
onloadeddata
onloadedmetadata
onloadstart
onpause
onplay
onplaying
onprogress
onratechange
onseeked
onseeking
onstalled
onsuspend
ontimeupdate
توضیحات | خواص | تگ |
---|---|---|
تعریف یک لینک anchor | target - href - hreflang - rel - onload - media - type | <a> |
تعریف ارتباط بین یک سند و یک منبع خارجی (معمولاً برای الحاق css به سند html) | target - hreflang - rel - onload - media - type - sizes | <link> |
تعریف مجموعه لینک های ناوبری | ----- | <nav> |
توضیحات | خواص | تگ |
---|---|---|
تعریف یک لیست غیر ترتیبی | <ul> | |
تعریف یک لیست ترتیبی | <ol> | |
تعریف یک آیتم لیست | <li> | |
تعریف یک لیست توضیحی | <dl> | |
تعریف نام یا اصطلاح (تیتر عنوان) درون لیست توضیحی | <dt> | |
تعریف توضیحات مربوط به لیست توضیحی | <dd> |
توضیحات | خواص | تگ |
---|---|---|
تعریف یک جدول | ----- | <table> |
تعریف عنوان یا برچسب جدول | ----- | <caption> |
تعریف سلول هدر درون یک جدول | colspan - headers - rowspan - scope | <th> |
تعریف یک ردیف در جدول | ----- | <tr> |
تعریف یک سلول در جدول | colspan - headers - rowspan | <td> |
گروه بندی محتوای سرصفحه | ------ | <thead> |
گروه بندی محتوای بدنه جدول | ------ | <tbody> |
گروه بندی محتوای فوتر جدول | ------ | <tfoot> |
خصوصیات ستون را برای هر ستون در داخل عنصر <colgroup> را مشخص می کند | ------ | <col> |
یک گروه از یک یا چند ستون برای قالب بند درون جدول مشخص میکند | ----- | <colgroup> |
توضیحات | خواص | تگ |
---|---|---|
تعریف کدهای استایل css درون سند html | media - onerror - onload - type | <style> |
تعریف یک بخش یا بلوک درون سند | <div> | |
تعریف یک بخش یا بلوک درون سند | <span> | |
تعریف هدر برای یک سند یا یک بخش | <header> | |
تعریف فوتربرای یک سند یا یک بخش | <footer> | |
محتوای اصلی یک سند را مشخص می کند | <main> | |
تعریف یک بخش درون یک سند | <section> | |
تعریف یک مقاله | <article> | |
محتوای کناری را جدا از محتوای صفحه تعریف میکند | <details> | |
جزئیات دیگری را تعریف میکند که کاربر میتواند آنرا مشاهده یا مخفی کند | <dialog> | |
یک کادر یا پنجره گفتگو را تعریف میکند | <summary> | |
میتوان یک محتوای قابل خواندن یا قابل ترجمه توسط ماشین های مترجم را ایجاد نمود. | <data> |
توضیحات | خواص | تگ |
---|---|---|
اطلاعات مربوط به سند را تعریف میکند | ---- | <head> |
متادیتا (اطلاعات متا/ابر داده) را در مورد یک سند html تعریف میکند | charset - content - http-equiv - name | <meta> |
مسیر اصلی مربوط به یک سند html را برای کلیه url های نسبی در یک سند مشخص میکند | href - target | <base> |
توضیحات | خواص | تگ |
---|---|---|
تگ های اسکریپت سمت کلاینت | async - charset - defer - onerror - onload - src - type | <script> |
محتوای جایگزین برای کاربرانی که مرورگرشان از جاوااسکریپت پشتیبانی نمیکند | ----- | <noscript> |
یک ظرف را برای یک برنامه خارجی (غیر html) تعریف میکند | onerror - src - type - height - onabort - oncanplay - width | <embed> |
یک شی تعبیه شده را تعریف میکند مثلاً فلش swf | onerror - src - type - height - onabort - oncanplay - width form | <object> |
پارامتری را برای یک شی تعریف میکند | ----- | <param> |
توضیحات | خواص | تگ |
---|---|---|
تگ های اسکریپت سمت کلاینت | async - charset - defer - onerror - onload - src - type | <script> |
محتوای جایگزین برای کاربرانی که مرورگرشان از جاوااسکریپت پشتیبانی نمیکند | ----- | <noscript> |
یک ظرف را برای یک برنامه خارجی (غیر html) تعریف میکند | onerror - src - type - height - onabort - oncanplay - width | <embed> |
یک شی تعبیه شده را تعریف میکند مثلاً فلش swf | onerror - src - type - height - onabort - oncanplay - width form | <object> |
پارامتری را برای یک شی تعریف میکند | ----- | <param> |
پروفایل
نکات، تجربیات، محتوای آموزشی و مطالب گردآوری شده در حوزه برنامه نویسی وب