وعده زنجیر زدن
بیایید به مسئله ذکر شده در فصل مقدمه: تماس های برگشتی : ما یک توالی از کارهای ناهمزمان داریم که یکی پس از دیگری انجام می شوند - به عنوان مثال بارگذاری اسکریپت ها. چگونه می توانیم آن را به خوبی کدگذاری کنیم؟
وعده ها برای انجام این کار چند دستور العمل ارائه می دهند.
در این فصل ما زنجیره وعده را پوشش می دهیم.
به نظر می رسد مانند این:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});ایده این است که نتیجه از طریق زنجیره .thenدستیاران منتقل می شود.
در اینجا جریان است:
- قول اولیه در 1 ثانیه برطرف می شود (*)،
- سپس .thenدستگیرنده نامیده می شود (**).
- مقدار بازگشتی آن به .thenدسترنج بعدی منتقل می شود(***)
- ... و غیره
در نتیجه در امتداد زنجیره ای از گرداننده گذشت، ما می توانیم یک دنباله از دیدن alertتماس: 1→ 2→ 4.
همه چیز کار می کند ، زیرا یک تماس برای promise.thenبازگشت یک وعده ، به طوری که ما می توانیم مورد بعدی را .thenبر روی آن بنامیم.
هنگامی که یک دستگیره مقدار را برمی گرداند ، نتیجه آن قول می شود ، بنابراین مورد بعدی .thenبا آن فراخوانی می شود.
یک خطای کلاسیک Newbie: از لحاظ فنی نیز می توان بسیاری را .thenبه یک قول واحد اضافه کرد. این زنجیر نیست.
مثلا:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});کاری که ما در اینجا انجام دادیم فقط چند انتقال دهنده به یک وعده است. آنها نتیجه را به یکدیگر منتقل نمی کنند. در عوض آنها به طور مستقل پردازش می کنند.
در اینجا تصویر (آن را با زنجیر بالا مقایسه کنید):
نتیجه همه .thenوعده ها - نتیجه همان وعده است. بنابراین در کد بالا همه alertیکسان را نشان می دهد 1:.
در عمل ، ما به ندرت برای یک وعده به چندین دستکار نیاز داریم. زنجیر زدن بیشتر اوقات استفاده می شود.
بازگشت وعده ها
یک کنترل کننده ، که در آن استفاده می شود ، .then(handler)ممکن است یک وعده را ایجاد کند و برگرداند
در این حالت ، دست اندرکاران دیگر صبر می کنند تا زمان حل و فصل آن ، نتیجه خود را بدست آورند.
برای مثال:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});در اینجا اولین .thenنمایش ها 1و بازگشت new Promise(…)در خط (*). پس از یک ثانیه حل می شود ، و نتیجه (استدلال resolve، در اینجا آن است result * 2) به کنترل کننده دوم منتقل می شود .then. آن هندلر در خط است (**)، همین کار را نشان می دهد 2و انجام می دهد.
بنابراین خروجی همانند مثال قبلی است: 1 → 2 4 اما اکنون با 1 ثانیه تأخیر بین alertتماسها.
بازگشت وعده ها به ما امکان می دهد زنجیره ای از اقدامات ناهمزمان ایجاد کنیم.
مثال: loadScript
بیایید از این ویژگی با قول مصور loadScriptتعریف شده در فصل قبل استفاده کنیم تا اسکریپت ها را یک به یک بار بارگیری کنیم ، به ترتیب:
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// use functions declared in scripts
// to show that they indeed loaded
one();
two();
three();
});این کد را می توان با عملکردهای فلش کمی کوتاه تر کرد:
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// scripts are loaded, we can use functions declared there
one();
two();
three();
});در اینجا هر loadScriptتماس یک وعده را برمی گرداند و .thenوقتی حل می شود بعدی اجرا می شود. سپس بارگذاری فیلمنامه بعدی را آغاز می کند. بنابراین اسکریپت ها یکی پس از دیگری بارگیری می شوند.
ما می توانیم اقدامات ناهمزمان بیشتری را به زنجیره اضافه کنیم. لطفاً توجه داشته باشید که کد هنوز "صاف" است - نه به سمت راست ، رشد می کند. هیچ نشانه ای از "هرم عذاب" وجود ندارد.
از نظر فنی می توانیم .thenمستقیماً به هریک اضافه کنیم loadScript، مانند این:
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// this function has access to variables script1, script2 and script3
one();
two();
three();
});
});
});این کد همین کار را انجام می دهد: 3 اسکریپت را به ترتیب دنبال می کند. اما "به راست" رشد می کند. بنابراین ما همان مشکلی را که در رابطه با تماس با ما وجود دارد ، داریم.
افرادی که شروع به استفاده از وعده ها می کنند ، گاهی اوقات از زنجیر زدن اطلاع ندارند ، بنابراین آنها را به این روش می نویسند. به طور کلی زنجیر ترجیح داده می شود.
بعضی اوقات نوشتن .thenمستقیم اشکالی ندارد ، زیرا تابع تو در تو به فضای بیرونی دسترسی دارد. در مثال بالا ترین پاسخ به تماس های تو در تو دسترسی به تمام متغیرهای دارد script1، script2، script3. اما این یک استثناء است نه یک قاعده.
موارد قابل استفاده
به طور دقیق ، ممکن است یک کنترل کننده دقیقاً قول داده نشود ، بلکه یک شیء به اصطلاح "قابل جابجایی" باشد - یک شیء دلخواه که دارای روشی است .then. با همان وعده رفتار خواهد شد.
ایده این است که کتابخانه های شخص ثالث ممکن است اشیاء "سازگار با وعده" را به کار گیرند. آنها می توانند مجموعه گسترده ای از روش ها را داشته باشند ، اما همچنین با وعده های بومی سازگار باشند ، زیرا آنها عملی می شوند .then.
در اینجا نمونه ای از یک شی قابل استفاده وجود دارد:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after the 1 second
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // shows 2 after 1000msجاوا اسکریپت شیء برگشت داده شده توسط .thenدستگیره را بررسی می کند (*): اگر روشی فراخوانی شده به نام خود داشته باشد then، آنگاه آن متد را برای ارائه توابع بومی resolve، rejectبه عنوان آرگومان (شبیه به مجری) می نامد و منتظر می ماند تا یکی از آنها فراخوانی شود. در مثال بالا resolve(2)بعد از 1 ثانیه گفته می شود (**). سپس نتیجه به پایین زنجیره منتقل می شود.
این ویژگی به ما امکان می دهد بدون نیاز به ارث بردن ، اشیاء سفارشی را با زنجیره های وعده ادغام کنیم Promise.
مثال بزرگتر: واکشی
در برنامه های مقدماتی وعده های برنامه نویسی اغلب برای درخواست های شبکه استفاده می شود. بنابراین بیایید یک مثال گسترده از آن را ببینیم.
ما از روش واکشی برای بارگیری اطلاعات مربوط به کاربر از سرور از راه دور استفاده خواهیم کرد. دارای بسیاری از پارامترهای اختیاری است که در فصل های جداگانه پوشانده شده است ، اما نحو اساسی بسیار ساده است:
let promise = fetch(url);این باعث می شود یک درخواست شبکه به urlوعده داده شود. responseوقتی سرور از راه دور با هدر پاسخ می دهد ، وعده با یک شیء حل می شود ، اما قبل از بارگیری کامل پاسخ .
برای خواندن پاسخ کامل ، باید از این روش استفاده کنیم response.text(): این نوید را برمی گرداند که هنگام بارگیری متن کامل از سرور از راه دور ، با نتیجه آن متن برطرف می شود.
کد زیر درخواستی را وارد کرده user.jsonو متن آن را از سرور بارگیری می کند:
fetch('/article/promise-chaining/user.json')
// .then below runs when the remote server responds
.then(function(response) {
// response.text() returns a new promise that resolves with the full response text
// when it loads
return response.text();
})
.then(function(text) {
// ...and here's the content of the remote file
alert(text); // {"name": "iliakan", "isAdmin": true}
});responseشئ بازگشتی fetchهمچنین شامل روش response.json()است که می خواند داده از راه دور و آن را تجزیه به صورت JSON. در مورد ما که این حتی راحت تر است ، بنابراین اجازه دهید به آن تغییر دهید.
ما همچنین برای توابع کوتاه از توابع فلش استفاده خواهیم کرد:
// same as above, but response.json() parses the remote content as JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan, got user nameحالا بیایید با کاربر لود شده کاری انجام دهیم.
به عنوان مثال ، می توانیم یک درخواست دیگر به GitHub ارسال کنیم ، نمایه کاربر را بارگذاری کنیم و نماد را نشان دهیم:
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to GitHub
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});کد کار می کند؛ مشاهده نظرات در مورد جزئیات. با این حال ، یک مشکل احتمالی در آن وجود دارد ، یک خطای معمولی برای کسانی که شروع به استفاده از وعده ها می کنند.
به خط نگاه کنید (*): چگونه می توانیم کاری کنیم که بعد از اتمام نمایش و حذف شدن نماد ، کاری انجام دهیم ؟ به عنوان مثال ، ما می خواهیم یک فرم برای ویرایش آن کاربر یا چیز دیگری نشان دهیم. در حال حاضر ، هیچ راهی وجود ندارد.
برای گسترش زنجیره ، باید قولی را برگردانیم که با پایان یافتن نماد ، حل شود.
مثل این:
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) { // (*)
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser); // (**)
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));این است که، .thenکنترل در خط (*)در حال حاضر بازده new Promise، که می شود تنها پس از تماس از حل و فصل resolve(githubUser)در setTimeout (**). مورد بعدی .thenدر زنجیره منتظر آن است.
به عنوان یک عمل خوب ، یک عمل ناهمزمان همیشه باید یک وعده را برگرداند. این باعث می شود برنامه ریزی اقدامات پس از آن انجام شود. حتی اگر اکنون قصد نداریم زنجیره را گسترش دهیم ، ممکن است بعداً به آن احتیاج پیدا کنیم.
در آخر ، ما می توانیم کد را به توابع قابل استفاده مجدد تقسیم کنیم:
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// Use them:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...خلاصه
اگر یک دستگیرنده .then(یا catch/finallyمهم نیست) یک قول را برمی گرداند ، بقیه زنجیره تا زمان حل و فصل منتظر می ماند. وقتی این کار را انجام داد ، نتیجه (یا خطای) آن منتقل می شود.
در اینجا یک تصویر کامل وجود دارد:
وظایف
قول: سپس در مقابل گرفتن
آیا این قطعات کد برابر هستند؟ به عبارت دیگر ، آیا آنها در هر شرایطی ، برای هر کارکرد کنترل کننده رفتار می کنند؟
promise.then(f1).catch(f2);در مقابل:
promise.then(f1, f2);راه حل
منبع