Разбиране на javascript обещанията; стекове и вериги

Сблъсквал съм се с няколко проблема с обещанията на javascript, особено с подредени вериги.

Може ли някой да ми обясни разликата (ако има такава!) между тези различни реализации?

ИЗПЪЛНЕНИЕ 1

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
}).then(function(response) {
    console.log('2', response);
    return true;
}).then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
}).then(function(response) {
    console.log('4', response);
    return async4();
})

ИЗПЪЛНЕНИЕ 2

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

ИЗПЪЛНЕНИЕ 3

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

Фактът, че част от веригата връща стойност („истина“ в стъпка 2), променя ли поведението? Обещанията изискват ли всички върнати стойности да бъдат асинхронни обещания, за да се запази поведението?


person Federico    schedule 24.04.2015    source източник


Отговори (3)


Вие илюстрирате разликата между веригата и разклоняването. Верижното свързване ще подреди множество асинхронни операции, така че една да започне, когато предишната завърши и можете да верижирате произволен брой елементи, които да подреждате един след друг.

Разклоняването свързва множество асинхронни операции, за да бъдат всички в движение по едно и също време, когато една задействаща операция завърши.

Реализации 1 и 3 са еднакви. Оковани са. Реализация 3 просто използва временна променлива за верига, докато реализация 1 просто използва върнатата стойност от .then() директно. Без разлика в изпълнението. Тези .then() манипулатори ще бъдат извикани по сериен начин.

Реализация 2 е различна. Тя е разклонена, а не верижна. Тъй като всички последващи .then() манипулатори са прикрепени към абсолютно същото serverSidePromiseChain обещание, всички те чакат само първото обещание да бъде разрешено и след това всички последващи асинхронни операции са в движение едновременно (не серийно, както в другите две опции).


Може да е полезно да разберете това, като се потопите едно ниво надолу в това как работи това с обещанията.

Когато го направите (сценарии 1 и 3):

p.then(...).then(...)

Това, което се случва е следното:

  1. Интерпретаторът взема вашата променлива p, намира метода .then() върху нея и го извиква.
  2. Методът .then() просто съхранява обратното извикване, което е предадено, и след това връща нов обещаващ обект. В момента не извиква обратното си извикване. Този нов обект на обещание е свързан както с оригиналния обект на обещание, така и с обратното извикване, което е съхранило. Няма да се реши, докато и двамата не са удовлетворени.
  3. След това се извиква вторият манипулатор .then() на това ново върнато обещание. Отново манипулаторът .then() на това обещание просто съхранява обратните извиквания .then() и те все още не са изпълнени.
  4. Тогава някъде в бъдещето оригиналното обещание p се разрешава чрез собствена асинхронна операция. Когато бъде разрешен, той извиква всички resolve манипулатори, които съхранява. Един от тези манипулатори ще бъде обратното извикване към първия манипулатор .then() в горната верига. Ако това обратно извикване се изпълнява до завършване и връща или нищо, или статична стойност (напр. не връща самото обещание), тогава то ще разреши обещанието, което е създадено, да се върне след първото извикване на .then(). Когато това обещание бъде разрешено, то ще извика манипулаторите за разрешаване, инсталирани от втория манипулатор .then() по-горе и така нататък.

Когато направите това (сценарий 2):

p.then();
p.then();

Единственото обещание p тук има съхранени манипулатори за разрешаване от двете .then() извиквания. Когато това оригинално обещание p бъде разрешено, то ще извика и двата манипулатора .then(). Ако самите манипулатори .then() съдържат асинхронен код и обещания за връщане, тези две асинхронни операции ще се изпълняват едновременно (паралелно подобно поведение), а не в последователност, както в сценарий 1 и 3.

person jfriend00    schedule 24.04.2015
comment
Реализация 2 е различна. Тя е разклонена, а не верижна. Тъй като всички следващи .then() манипулатори са прикачени към точно същото обещание serverSidePromiseChain, всички те чакат само първото обещание да бъде разрешено (за мен) е най-краткият и ясен отговор. Много добре казано. Благодаря! - person Federico; 24.04.2015
comment
Казвате, че разклоняването свързва множество операции, за да се изпълняват всички паралелно, когато една и съща операция завърши. Това е подвеждащо, тъй като може да се разчете като казващо, че манипулаторите се изпълняват паралелно, което не е вярно (манипулаторите се изпълняват в една и съща нишка, последователно, в същия ред, в който съответните thens бяха извикани). По същия начин, последното изречение, тези два манипулатора .then() ще работят паралелно, е още по-ясно погрешно: не, манипулаторите .then() не работят паралелно, но асинхронната работа, която планират, може. - person Don Hatch; 27.10.2016
comment
@DonHatch - Донякъде промених формулировката, но тук малко се заяждаш. Концепцията тук е, че разклоняването позволява всички асинхронни операции да работят едновременно (често наричани паралелни), докато верижното налагане налага стриктна серийна операция, така че втората асинхронна операция дори не е стартирана, докато първата не е разрешена. Да, JS е с една нишка, така че нито един JS код всъщност не работи паралелно при никакви обстоятелства, но самите асинхронни операции са в процес на полет едновременно, което е паралелно подобно поведение за асинхронните операции, а не серийно поведение. - person jfriend00; 27.10.2016
comment
Благодаря. Мисля, че това е важно разграничение и не е непременно очевидно, особено за хора (като мен), които използват вашия отговор като материал, за да научат и затвърдят разбирането си за това как работят javascript и обещанията. Благодаря, че ме усмихна. Сега формулировката ми изглежда много добра. - person Don Hatch; 27.10.2016

Изпълнение #1 и #3 са еквивалентни. Реализация #2 се различава, тъй като там няма верига и всички обратни извиквания ще бъдат изпълнени на едно и също обещание.

Сега нека поговорим малко за обещаващите вериги. спецификации казват, че:

2.2.7 then трябва да върне обещание
2.2.7.1 Ако onFulfilled или onRejected върне стойност x, стартирайте процедурата за разрешаване на обещание [[Resolve]](promise2, x)
2.3.3 Ако x е обещание, приемете състоянието му

По принцип извикването на then на обещание връща друго promise, което се разрешава/отхвърля въз основа на callback return value. Във вашия случай вие връщате скаларни стойности, които след това се разпространяват надолу по веригата до следващото обещание.

Във вашия конкретен случай ето какво се случва:

  • #1: имате 7 обещания (async кол плюс 4 then, плюс две от async3()/async4), serverSidePromiseChain ще посочи последното обещание, върнато от then. Сега, ако обещанието, върнато от async(), никога не бъде разрешено/отхвърлено, тогава serverSidePromiseChain също ще бъде в същата ситуация. Същото с async3()/async4(), ако това обещание също не е разрешено/отхвърлено
  • #2: then се извиква няколко пъти за едно и също обещание, създават се допълнителни обещания, но те не влияят на потока на приложението. След като обещанието, върнато от async(), бъде разрешено, всички обратни извиквания ще бъдат изпълнени, това, което връщат обратното извикване, ще бъде отхвърлено
  • #3: това е еквивалентно на #1, само че сега изрично предавате създадените обещания. Когато обещанието, върнато async(), бъде разрешено, първото обратно извикване ще бъде изпълнено, което разрешава следващото обещание с true, второто обратно извикване ще направи същото, третото ще има шанса да преобразува веригата в неуспешна, ако обещанието на async3() се отхвърля, същото с обратното извикване, което връща обещанието на async4().

Веригите Promise са най-подходящи за действителни асинхронни операции, при които операциите зависят от резултатите от предишната и не искате да пишете много лепен код и също така не искате да достигате до ад за обратно извикване.

Написах поредица от статии в моя блог за обещания, една, описваща функцията за верижно свързване на обещания, може да бъде намерена тук; статията е насочена към ObjectiveC, но принципите са същите.

person Cristik    schedule 24.04.2015

Изпълнение 1 и 3 изглеждат еквивалентни.

В изпълнение 2 последните 3 .then() функции действат на едно и също обещание. Методът .then() връща ново обещание. Стойността на изпълнено обещание не може да бъде променена. Вижте Promises/A+ 2.1.2.2. Вашият коментар в изпълнение 2, този отговор се очаква да е верен, показва, че очаквате друго. Не, response няма да е вярно (освен ако това не е стойността от първоначалното обещание).

Нека просто го изпробваме. Изпълнете следния кодов фрагмент, за да видите разликите:

function async(){ return Promise.resolve("async"); }
function async3(){ return Promise.resolve("async3"); }
function async4(){ return Promise.resolve("async4"); }


function implementation1() {
  logContainer = document.body.appendChild(document.createElement("div"));
  console.log("Implementation 1");
  var serverSidePromiseChain;
  serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
  }).then(function(response) {
    console.log('2', response);
    return true;
  }).then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
  }).then(function(response) {
    console.log('4', response);
    return async4();
  });
}

function implementation2() {
  logContainer = document.body.appendChild(document.createElement("div"));
  console.log("Implementation 2");
  var serverSidePromiseChain;
  serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
  });
  serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
  });
  serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
  });
  serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
  });
}

function implementation3() {
  logContainer = document.body.appendChild(document.createElement("div"));
  console.log("Implementation 3");
  var serverSidePromiseChain;
  serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
  });
  serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
  });
  serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
  });
  serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
  });
}

var logContainer;
var console = {
  log: function() {
    logContainer.appendChild(document.createElement("div")).textContent = [].join.call(arguments, ", ");
  }
};

onload = function(){
  implementation1();
  setTimeout(implementation2, 10);
  setTimeout(implementation3, 20);
}
body > div {
  float: left;
  font-family: sans-serif;
  border: 1px solid #ddd;
  margin: 4px;
  padding: 4px;
  border-radius: 2px;
}

person gilly3    schedule 24.04.2015