Javascript обіцяє цікавість


96

Коли я називаю цю обіцянку, вихід не відповідає послідовності викликів функцій. .thenПриходить до того , як .catch, не дивлячись на те, обіцянку з .thenбуло називатися після. У чому причина цього?

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

verifier(5, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

вихід

node promises.js
response: true
error: false

34
Ніколи не слід покладатися на терміни між незалежними ланцюжками обіцянок.
Бергі

Відповіді:


136

Це якось круте запитання, до якого можна дійти.

Коли ви робите це:

verifier(3,4).then(...)

що повертає нову обіцянку, яка вимагає ще одного циклу назад до циклу подій, перш ніж ця нещодавно відхилена обіцянка зможе запустити .catch()обробник, який слід. Цей додатковий цикл дає наступну послідовність:

verifier(5,4).then(...)

шанс запустити свій .then()обробник до попереднього рядка, .catch()оскільки він уже був у черзі до того, як .catch()обробник з першого потрапить у чергу, і елементи запускаються з черги у порядку FIFO.


Зверніть увагу, що якщо ви використовуєте .then(f1, f2)форму замість .then().catch(), вона дійсно запускається тоді, коли ви цього очікуєте, оскільки немає додаткових обіцянок і, отже, жодної додаткової галочки:

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response (3,4): ", response),
        (error) => console.log("error (3,4): ", error)
  );

verifier(5, 4)
  .then((response) => console.log("response (5,4): ", response))
  .catch((error) => console.log("error (5,4): ", error));

Зверніть увагу, я також позначив усі повідомлення, щоб ви могли бачити, з якого verifier()дзвінка вони надходять, що полегшує читання результатів.


ES6 Специфікація щодо замовлення зворотного виклику та більш детальне пояснення

Специфікація ES6 повідомляє нам, що обіцяні "завдання" (як це викликає зворотний виклик з .then()або .catch()) виконуються у порядку FIFO на основі того, коли вони вставляються в чергу завдань. Він конкретно не називає FIFO, але вказує, що нові завдання вставляються в кінці черги, а завдання виконуються з початку черги. Це реалізує замовлення FIFO.

PerformPromiseThen (який виконує зворотний виклик з .then()) призведе до EnqueueJob, тобто, як обробник вирішення або відхилення планується фактично запустити. EnqueueJob вказує, що очікуване завдання додається в зворотному боці черги завдань. Потім операція NextJob витягує елемент з передньої частини черги. Це забезпечує порядок FIFO при обслуговуванні завдань із черги завдань Promise.

Отже, у прикладі у вихідному питанні ми отримуємо зворотні виклики для verifier(3,4)обіцянки та verifier(5,4)обіцянки, вставлених у чергу завдань у тому порядку, в якому вони були запущені, оскільки обидві ці початкові обіцянки виконані. Потім, коли перекладач повертається до циклу подій, він спочатку бере verifier(3,4)роботу. Цю обіцянку відхилено, і для неї немає зворотного виклику verifier(3,4).then(...). Отже, що він робить, це відхиляє обіцянку, яка verifier(3,4).then(...)повернулася, і що призводить до того, що verifier(3,4).then(...).catch(...)обробник буде вставлений у jobQueue.

Потім він повертається до циклу подій, і наступним завданням, яке він витягує з jobQueue, є verifier(5, 4)завдання. Це має вирішену обіцянку та обробник вирішення, тому він викликає цей обробник. Це призводить до відображення response (5,4):вихідних даних.

Потім він повертається до циклу подій, і наступним завданням, яке він витягує з jobQueue, є verifier(3,4).then(...).catch(...)завдання, де воно виконується, і це призводить error (3,4)до відображення результату.

Це тому, що .catch()1-й ланцюжок - це один рівень обіцянки, глибший у своєму ланцюгу, ніж .then()2-й ланцюжок, що спричинює порядок, про який ви повідомили. І це тому, що ланцюжки обіцянок переходять від одного рівня до наступного через чергу завдань у порядку FIFO, а не синхронно.


Загальна рекомендація щодо покладання на цей рівень деталізації планування

FYI, загалом, я намагаюся писати код, який не залежить від цього рівня детальних знань часу. Хоча це цікаво і іноді корисно розуміти, це крихкий код, оскільки проста, здавалося б, нешкідлива зміна коду може призвести до зміни відносних термінів. Отже, якщо хронометраж є критичним між двома ланцюжками, як це, то я волів би писати код таким чином, щоб змусити хронометраж так, як я хочу, а не покладатися на цей рівень детального розуміння.


Якщо бути більш конкретним, ця точна поведінка ніде не задокументована у специфікації обіцянок, що робить це деталлю реалізації. Ви можете отримати різну поведінку між інтерпретаторами (наприклад, Node.js проти Edge проти Firefox) або між версіями інтерпретаторів (наприклад, Node 12 проти Node 14). У специфікації просто сказано, що обіцянки обробляються асинхронно, щоб уникнути zalgo-коду (який IMHO було помилково введено, оскільки це було мотивовано людьми, які задавали подібні запитання, бажаючи залежати від часу потенційно асинхронного коду)
slebetman

@slebetman - Чи не задокументовано, що зворотні виклики обіцянок з окремих обіцянок називаються FIFO на основі того, коли вони були вставлені в чергу і не можуть працювати до наступного галочки? Здається, що замовлення FIFO - це все, що тут потрібно, тому що .then()має повернути нову обіцянку, яка сама повинна вирішити / відхилити асинхронно на майбутньому галочці, що і призводить до цього замовлення. Чи знаєте ви про будь-яку реалізацію, яка не використовує впорядкування FIFO конкуруючих зворотних викликів?
jfriend00

3
@slebetman Promises / A + цього не вказує. ES6 це вказує. (Однак ES11 змінив поведінку await).
Бергі,

З специфікації ES6 щодо порядку черги. PerformPromiseThenпризведе до того EnqueueJob, як саме планується викликати обробник вирішення або відхилення. EnqueueJob вказує, що очікуване завдання додається в зворотному боці черги завдань. Потім операція NextJob витягує елемент з передньої частини черги. Це забезпечує порядок FIFO в черзі завдань Promise.
jfriend00

@Bergi У чому полягає ця зміна awaitв ES11? Досить посилання. Дякую!!
Педро A

49

Promise.resolve()
  .then(() => console.log('a1'))
  .then(() => console.log('a2'))
  .then(() => console.log('a3'))
Promise.resolve()
  .then(() => console.log('b1'))
  .then(() => console.log('b2'))
  .then(() => console.log('b3'))

Замість виводу a1, a2, a3, b1, b2, b3 ви побачите a1, b1, a2, b2, a3, b3 з тієї ж причини - кожен раз повертає обіцянку, і вона переходить до кінця циклу подій черга. Тож ми можемо побачити цю "гонку обіцянок". Те саме відбувається, коли є деякі вкладені обіцянки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.