Кириш сөздөн кырдаалга кайрылып көрөлү : Кайра чалуулар : бизде биринин артынан бири аткарылышы керек болгон асинхрондук тапшырмалардын ырааттуулугу бар. Мисалы, биз скрипттерди жүктөө жөнүндө айта алабыз. Муну коддо кантип туура ишке ашыруу керек?
Убадалар бул милдетти аткаруунун бир нече жолдорун берет.
Бул бөлүмдө биз убадалардын чынжырын изилдейбиз.
Ал мындай көрүнөт:
@A@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;
});@A@
Идея биринчи убаданын натыйжасы башкаруучулардын чынжырында өтөт .then
.
Аткаруу агымы мындай:
- Алгачкы убада 1 секунддан кийин ишке ашат
(*)
, - Андан кийин иштетүүчү чакырылат
.then
(**)
. - Ал кайтарган маани кийинки иштеткичке берилет.
.then
(***)
- …жана башка.
Натыйжада, натыйжа иштетүүчүлөрдүн чынжырчасы боюнча өтүп, биз бир alert
катардан чыкканын көрөбүз: 1
→ 2
→ 4
Мунун баары иштейт, анткени чалуу promise.then
дагы убаданы кайтарат, андыктан кийинкиге чалсак болот .then
.
Иштеткыч маанини кайтарганда, ал тиешелүү убаданын аткарылышынын натыйжасы болуп калат жана кийинкиге өтөт .then
.
.then
Жаңы келгендердин классикалык катасы: бир убадага көптөгөн иштетүүчүлөрдү кошуу техникалык жактан мүмкүн . Бирок бул чынжыр эмес.
Мисалы:
@A@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;
});@A@
Биз бир убадага бир нече иштетүүчүлөрдү коштук. Алар аткарылышынын жыйынтыгын бири-бирине билдирбестен, өз алдынча аракеттенишет.
Бул жерде эмне болуп жаткандыгынын сүрөтү (жогоруда айтылган убада чынжырынын сүрөтү менен салыштырыңыз)
Бир эле убада боюнча иштегендердин баары .then
бирдей бааны алышат - ошол эле убаданы аткаруунун натыйжасы. Ошентип, жогорудагы коддо бардыгы alert
бирдей нерсени көрсөтөт: 1
.
Иш жүзүндө, бир убадага бир нече иштетүүчүлөрдү дайындоо сейрек зарыл. Бирок убадалардын чынжырчасы алда канча көп колдонулат.
промистеди кайтарабыз
handler
Өткөрүлгөн иштетүүчү .then(handler)
убаданы кайтара алат.
Бул учурда, кийинки иштетүүчүлөр анын аткарылышын күтүп, андан кийин анын натыйжасын алышат.
Мисалы:
@A@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
});@A@
Бул жерде биринчи сапта жаңы убаданы .then
көрсөтөт жана кайтарат . Бир секунддан кийин бул убада ийгиликтүү аткарылат жана анын натыйжасы (-деги аргумент , б.а. ) кийинки . Ал сапта турат , көрсөтөт жана ошондой кылат.1
new Promise(…)
(*)
resolve
result * 2
.then
(**)
2
Ошентип, мурунку мисалдагыдай, 1 → 2 → 4 чыгарылат, бирок азыр alert
чалуулар ортосунда 1 секунд тыныгуу бар.
Убадаларды кайтаруу менен биз асинхрондук аракеттердин чынжырларын түзө алабыз.
Мисал: loadScript
Келгиле, бул функцияны, биз мурунку бөлүмдөloadScript
түзүлгөн убадаланган функция менен бирге , скрипттерди кезек менен жүктөө үчүн колдонолу:
@A@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) {
// вызовем функции, объявленные в загружаемых скриптах,
// чтобы показать, что они действительно загрузились
one();
two();
three();
});@A@
Ошол эле кодду жебе функцияларын колдонуу менен бир аз кыскараак кайра жазса болот:
@A@loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// скрипты загружены, мы можем использовать объявленные в них функции
one();
two();
three();
});@A@
Бул жерде ар бир чалуу loadScript
убаданы кайтарат, ал эми кийинки иштетүүчү .then
ал убада аяктаганда гана күйөт. Андан кийин кийинки скрипт жүктөлөт жана башкалар. Ушундай жол менен скрипттер бирден жүктөлөт.
Биз чынжырга башка асинхрондук аракеттерди да кошо алабыз. Көңүл буруңуз, биздин код дагы эле "жалпак", ал оңго эмес, ылдыйга "өсөт". "Кыйынчылыктардын тозок пирамидасынын" белгилери жок.
.then
Техникалык жактан, биз ар бир чалууга түздөн-түз кошо алабыз loadScript
, мисалы:
@A@loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// эта функция имеет доступ к переменным script1, script2 и script3
one();
two();
three();
});
});
});@A@
Бул код да ушундай кылат: ал 3 скрипттерди ырааттуу түрдө жүктөйт. Бирок ал "оңго карай өсөт", ошондуктан кайра чалуулардагыдай эле көйгөй пайда болот.
Жакында убадаларды колдоно баштаган иштеп чыгуучулар кээде чынжырлар жөнүндө билишпейт жана кодду жогоруда көрсөтүлгөндөй жазышат. Жалпысынан алганда, убада чынжырларды колдонуу артыкчылыктуу болуп саналат.
Кээде түз эле кошуу алгылыктуу .then
, андыктан андагы функция сырткы чөйрөгө кире алат. Жогорудагы мисалда, эң терең уяланган кайра чалуу , script1
, script2
, бардык өзгөрмөлөргө кире алат script3
. Бирок бул эрежеге караганда өзгөчө болуп саналат.
Тагыраак айтканда, иштетүүчү так убаданы эмес, методду камтыган каалаган объектти кайтара алат .then
, мындай объекттер "андан кийин" деп аталат жана бул объект убада катары иштетилет.
Кеп үчүнчү тараптын китепканалары өз убадаларына шайкеш объекттерди түзө алат. Алар өз ыкмаларына ээ болушу мүмкүн жана дагы эле камтылган убадалар менен шайкеш келет, анткени алар .then
.
Мындай объекттин мисалы:
@A@class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// будет успешно выполнено с аргументом this.num*2 через 1 секунду
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // показывает 2 через 1000мс@A@
.then
JavaScript линиядагы иштеткичтен кайтарылган объектти текшерет (*)
: эгерде анын чакырылышы мүмкүн болгон ыкмасы болсо then
, анда ал ыкма чакырылат, ал эми орнотулган функциялар аргумент катары өткөрүлүп берилет resolve
, reject
алардын бири чакырылат. Жогорудагы мисалда чалуу resolve(2)
1 секунддан кийин болот (**)
. Натыйжа андан кийин чынжыр боюнча өткөрүлөт.
Бул ыңгайлаштырылган объекттерди убада чынжырларына кошууга мүмкүндүк берет, аларды мурастоого мажбурлабастан Promise
.
Татаал мисал: fetch
Front-end иштеп чыгууда, убадалар көбүнчө тармак аркылуу суроо-талаптарды жасоо үчүн колдонулат. Андай бир мисалды карап көрөлү.
Биз алыскы серверден колдонуучу маалыматын алуу үчүн алып келүү ыкмасын колдонобуз . Бул ыкманын көптөгөн кошумча параметрлери бар, алар тиешелүү бөлүмдөрдө талкууланат , бирок негизги синтаксиси абдан жөнөкөй:
@A@let promise = fetch(url);@A@
Бул код тармак аркылуу сурайт url
жана убаданы кайтарат. response
Убада ишке ашат жана өз кезегинде алыскы сервер жооп баштарын жөнөткөндөн кийин объектти кайтарат , бирок сервердин бүт жообу толугу менен жүктөлүп алынганга чейин .
Толук жоопту окуу үчүн, сиз ыкманы чакырышыңыз керек response.text()
: ал ошондой эле алыскы серверден маалыматтар толугу менен жүктөлүп алынганда аткарылуучу убаданы кайтарат жана бул маалыматтарды кайтарат.
Төмөндөгү код файлды сурайт user.json
жана анын мазмунун серверден жүктөп алат:
@A@fetch('/article/promise-chaining/user.json')
// .then в коде ниже выполняется, когда удалённый сервер отвечает
.then(function(response) {
// response.text() возвращает новый промис,
// который выполняется и возвращает полный ответ сервера,
// когда он загрузится
return response.text();
})
.then(function(text) {
// ...и здесь содержимое полученного файла
alert(text); // {"name": "iliakan", isAdmin: true}
});@A@
response.json()
JSON форматындагы маалыматтарды окуй турган ыкма да бар . Бул биздин мисалга ылайыктуу, ошондуктан аны колдонолу.
Кодубузду кыскараак жазуу үчүн жебе функцияларын да колдонобуз:
@A@// то же самое, что и раньше, только теперь response.json() читает данные в формате JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan, получили имя пользователя@A@
Эми алынган колдонуучу маалыматтары менен бир нерсе кылалы.
Мисалы, биз GitHub'га колдонуучунун профилинен маалыматтарды жүктөп алуу жана анын аватарын көрсөтүү өтүнүчүн жөнөтө алабыз:
@A@// Запрашиваем user.json
fetch('/article/promise-chaining/user.json')
// Загружаем данные в формате json
.then(response => response.json())
// Делаем запрос к GitHub
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Загружаем ответ в формате json
.then(response => response.json())
// Показываем аватар (githubUser.avatar_url) в течение 3 секунд (возможно, с анимацией)
.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); // (*)
});@A@
Код иштейт, ишке ашыруунун деталдары комментарийлерде чагылдырылат. Бирок, анын башталгычтар көп жолуккан бир потенциалдуу көйгөйү бар.
Сапты караңыз : аватар көрсөтүлүп, алынып салынгандан кийин(*)
кандай чара көрөбүз ? Мисалы, биз колдонуучунун түзөтүү формасын же башка нерсени көрсөткүбүз келет. Эми бул мүмкүн эмес.
Кодубузду кеңейтүү үчүн, аватар көрсөтүлүп бүткөндөн кийин аткарылган дагы бир убаданы кайтарышыбыз керек.
Болжол менен ушундай:
@A@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);
}))
// срабатывает через 3 секунды
.then(githubUser => alert(`Закончили показ ${githubUser.name}`));@A@
.then
Башкача айтканда, линиядагы иштеткич (*)
кайтып келет , ал new Promise
"аткарылган" абалга кийин гана кирет .setTimeout
(**)
resolve(githubUser)
Демек, чынжырдын кийинкиси .then
муну күтөт.
Жалпы эреже катары, бардык асинхрондук аракеттер убаданы кайтарып бериши керек.
Бул андан кийин кээ бир кошумча иш-аракеттерди пландаштырууга мүмкүндүк берет. Бул өзгөчөлүк азыр керек болбосо да, келечекте керек болушу мүмкүн.
Акыр-аягы, келгиле, жазылган кодду өзүнчө кайталануучу функцияларга бөлөлү:
@A@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);
});
}
// Используем их:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Показ аватара ${githubUser.name} завершён`));
// ...@A@
Бардыгы
Эгерде даректеги иштеткич .then
(же catch/finally
, кандай болсо да) убаданы кайтарса, чынжырдагы кийинки элементтер ал убаданын аткарылышын күтүшөт. Бул болгондо, анын аткарылышынын натыйжасы (же ката) өткөрүлүп берилет.
Бул жерде эмне болуп жатканын толук сүрөт:
Tasks
промис: салыштыргыла, then жана catch
Төмөндөгү код үзүндүлөрү эквиваленттүүбү? Башкача айтканда, алар бардык жагдайларда бирдей жүрүшөбү, анткени бардык башкаруучулар аларга өткөн?