Понимание JS Promises

Я хотел бы получить более глубокое понимание того, как Promises работает внутри. Поэтому у меня есть пример кода:

var p1 = new Promise(
function(resolve, reject) {       
  window.setTimeout(
    function() {
          resolve('res called')
    }, 2000);
});


var p2 = new Promise(
function(resolve, reject) {       
  window.setTimeout(
    function() {
          resolve('res called')
    },2000);
});


function chainPromises(){  
  return p1.then(function(val) {
           console.log("p1");
           return p2.then(function(val) {
                  console.log("p2");
                  return val;
            }); 
  });   
}

chainPromises().then(function(val) {
    console.log(val);
}
);

Здесь ссылка для выполнения этого кода.

Как и следовало ожидать, сначала p1 разрешается, потом p2, а в конце финальный затем выводит значение resolv.

Но в API ref указано следующее:

"then" returns a new promise equivalent to the value you return from 
onFulfilled/onRejected after being passed through Promise.resolve

Итак, было бы интересно узнать, КОГДА именно функция "then" выполняется? Поскольку окончательное "то" в коде привязано к цепочке Promises(), я сначала подумал, что он будет выполняться после того, как функция chainPromises() вернет что-то (в этом случае другое обещание).

Если бы это было так, то "val" окончательной "then" функции было бы возвращенным обещанием. Но вместо этого окончательное "то" ждет, пока все Promises внутри первого "затем", которые возвращаются, были разрешены. Это совершенно разумно, потому что таким образом "тогда" функции могут быть сложены, но Я действительно не понимаю, как это делается, поскольку спецификация API. на самом деле не покрывает то, что "затем" возвращается и когда выполняются "то" функции.

Или, другими словами, почему конечная "then" функция ждет, пока все Promises не будут разрешены внутри функции chainPromises(), а не просто ждут первого возвращенного объекта, как говорит API-документ.

Надеюсь, я мог бы понять, что я имею в виду..:)

Ответ 1

О разрешении обещаний

То, что вы видите здесь, называется рекурсивным then способным разрешением. Процесс разрешения обещаний в спецификации Promises/A + содержит следующее предложение:

onFulfilled или onRejected возвращает значение x, запускает процедуру разрешения обещания [[Resolve]] (prom2, x)

Спецификация обещания ES6 (promises разворачивание) содержит аналогичное предложение.

Это означает, что когда выполняется операция resolve: либо в конструкторе обещаний, вызывая Promise.resolve, либо в вашем случае в цепочке then реализация обещания должна рекурсивно разворачивать возвращенный если это обещание.

На практике

Это означает, что если onFulfilled (then) возвращает значение, попробуйте "разрешить" значение обещания самостоятельно, таким образом, рекурсивно ожидая всю цепочку.

Это означает следующее:

promiseReturning().then(function(){
    alert(1);
    return foo(); // foo returns a promise
}).then(function(){
    alert(2); // will only run after the ENTIRE chain of `foo` resolved
              // if foo OR ANY PART OF THE CHAIN rejects and it is not handled this 
              // will not run
});

Итак, например:

promiseReturning().then(function(){
    alert(1);
    return Promise.resolve().then(function(){ throw Error(); });
}).then(function(){
    alert("This will never run");
});

И что:

promiseReturning().then(function(){
    alert(1);
    return Promise.resolve().then(function(){ return delay(2000); });
}).then(function(){
    alert("This will only run after 2000 ms");
});

Это хорошая идея?

Это была тема много дискуссий в процессе спецификации promises, был рассмотрен второй цепной метод, который не демонстрирует этого поведения, но решил (пока недоступен в Chrome, но скоро будет удален). Вы можете прочитать о всех дискуссиях в этом esdiscuss thread. Это поведение по прагматическим причинам, поэтому вам не придется вручную это делать.

В других языках

Стоит отметить, что другие языки этого не делают, ни фьючерсы в Scala, ни задачи в С# не имеют этого свойства. Например, в С# вам нужно будет вызвать Task.Unwrap в задаче, чтобы дождаться, пока ее цепочка будет разрешена.

Ответ 2

Давайте начнем с легкой перспективы: "chainPromises" возвращает обещание, поэтому вы можете смотреть на него следующим образом:

// Do all internal promises
var cp = chainPromises();

// After everything is finished you execute the final "then".
cp.then(function(val) {
    console.log(val);
});

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

Итак, если "a" является обещанием, а "b" является обещанием:

// "a" "then" function will only be marked as finished after "b" "then" function has finished.  
var c = a.then(function () {
    return b.then(function () {
        console.log("B!");
    };
};

// c is a promise, since "then" always returns a promise.    
c.then(function() {
    console.log("Done!");
};

Таким образом, выход будет:

B! 
Done!

Обратите внимание, что если вы не вернете внутреннее обещание, это будет не так:

// "a" "then" function will only be marked as finished without waiting for "b" "then" to finish.  
var c = a.then(function () {
    // Notice we're just calling b.then, and don't "return" it. 
    b.then(function () {
        console.log("B!");
    };
};

// c is a promise, since "then" always returns a promise.    
c.then(function() {
    console.log("Done!");
};

Здесь мы не можем знать, что будет выводиться первым. Это может быть либо "B!" или "Готово!".

Ответ 3

Пожалуйста, проверьте следующий пример относительно того, как работают promises:

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

console.log('person1: shoe ticket');
console.log('person2: shoe ticket');

const promiseGirlFriendBringingTickets = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('ticket');
	}, 3000);
});

promiseGirlFriendBringingTickets.then((t) => {
	console.log('person3: show ${t}');
})

console.log('person4: shoe ticket');
console.log('person5: shoe ticket');

Ответ 4

Я не знаю, как это делается в реальных библиотеках promises, но я смог воссоздать эту функциональность следующим образом: 1) каждое обещание имеет свойство waitPromises; 2), тогда метод возвращает новое обещание, а оригинальное обещание, ожидающее свойства "Прогнозы", указывает на новое обещание.

Таким образом, цепочка .then() s создает структуру, которая похожа на связанный список или, скорее, дерево (каждое обещание может иметь несколько ожидающих promises). Обещание может быть разрешено только после того, как его "родительское" обещание было разрешено. Сам метод .then выполняется немедленно, но соответствующее обещание, которое оно создает, разрешается только позже. Я не уверен, что это хорошее объяснение, и мне бы хотелось узнать о других возможных подходах.

Ответ 5

Promise then return promise object, а не promise resolved value. Я разветкил JSFiddle и добавил некоторые из моих попробовал это.

promise.then выполняется сразу после этого promise object.

Ответ 6

Обычно код является синхронным - один оператор выполняется как (fileopen), и есть гарантия, что следующий оператор будет выполняться сразу же после, как filewrite()

но в асинхронных операциях, таких как nodejs, вы должны предполагать, что

  • Вы не знаете, когда операция завершится.
  • Вы даже не можете предположить, что только потому, что вы сначала отправили один запрос, а затем другой запрос, они вернутся в этом порядке.
  • Обратные вызовы являются стандартным способом обработки асинхронного кода в JavaScript
  • но обещания являются лучшим способом обработки асинхронного кода.
  • Это потому, что обратные вызовы затрудняют обработку ошибок и приводят к уродливому вложенному коду.
  • какой пользователь и программист не читаются легко, так что обещания это путь

Ответ 7

Вы можете думать об Promise как обёртке для некоторой фоновой задачи. Он принимает функцию, которая должна выполняться в фоновом режиме.

Наиболее подходящим местом для использования обещания является тот, где некоторый код зависит от некоторой фоновой обработки, и ему необходимо знать состояние фоновой задачи, которая была выполнена. Для этого, сами фоновой задача принимает два обратных вызов resolve и reject от того, чтобы передать свой статус в код, который зависит от него. С точки зрения непрофессионала, этот код является единственным в цепочке обещаний.

Когда фоновая задача вызывает resolve обратный вызов с каким - либо параметром. он отмечает успешную фоновую операцию и передает результат фоновой операции в следующий блок then который будет выполнен следующим. и если он вызывает reject, помечая его как неудачный, будет выполнен первый блок catch. В своем пользовательском promise вы можете передать ошибку obj в обратный вызов отклонения, чтобы следующий блок catch узнал об ошибке, произошедшей в фоновой задаче.