Loading...

Промис  чынжыры

Промис  чынжыры

 

Кириш сөздөн кырдаалга кайрылып көрөлү : Кайра чалуулар : бизде биринин артынан бири аткарылышы керек болгон асинхрондук тапшырмалардын ырааттуулугу бар. Мисалы, биз скрипттерди жүктөө жөнүндө айта алабыз. Муну коддо кантип туура ишке ашыруу керек?

Убадалар бул милдетти аткаруунун бир нече жолдорун берет.

Бул бөлүмдө биз убадалардын чынжырын изилдейбиз.

Ал мындай көрүнөт:

@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. Алгачкы убада 1 секунддан кийин ишке ашат (*),
  2. Андан кийин иштетүүчү чакырылат .then (**).
  3. Ал кайтарган маани кийинки иштеткичке берилет..then (***)
  4. …жана башка.

Натыйжада, натыйжа иштетүүчүлөрдүн чынжырчасы боюнча өтүп, биз бир 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көрсөтөт жана кайтарат . Бир секунддан кийин бул убада ийгиликтүү аткарылат жана анын натыйжасы (-деги аргумент , б.а. ) кийинки . Ал сапта турат , көрсөтөт жана ошондой кылат.1new Promise(…)(*)resolveresult * 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, андыктан андагы функция сырткы чөйрөгө кире алат. Жогорудагы мисалда, эң терең уяланган кайра чалуу , script1script2, бардык өзгөрмөлөргө кире алат script3. Бирок бул эрежеге караганда өзгөчө болуп саналат.

Thenable

Тагыраак айтканда, иштетүүчү так убаданы эмес, методду камтыган каалаган объектти кайтара алат .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@

.thenJavaScript линиядагы иштеткичтен кайтарылган объектти текшерет (*): эгерде анын чакырылышы мүмкүн болгон ыкмасы болсо then, анда ал ыкма чакырылат, ал эми орнотулган функциялар аргумент катары өткөрүлүп берилет resolverejectалардын бири чакырылат. Жогорудагы мисалда чалуу 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

 

Төмөндөгү код үзүндүлөрү эквиваленттүүбү? Башкача айтканда, алар бардык жагдайларда бирдей жүрүшөбү, анткени бардык башкаруучулар аларга өткөн?