Как обеименные цепочки ссылаются на следующий обработчик ошибок

Мне интересно, знает ли кто-нибудь, как цепочки обещаний ссылаются на следующий обработчик ошибок - например:

  const p = new Promise(resolve => resolve(5))
   .then(v => 5*v)
   .then(v => { throw 'foo' });

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('first catch:', e));

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('second catch:', e));

Если вы запустите это, вы получите:

first catch: foo
second catch: foo

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

Кто-нибудь знает, как это реализовано?

Ответы

Ответ 1

Как я вижу это:

Думайте о обещаниях как вложенных массивах:

[then1,[then1.1, then1.2, catch1.1, then1.3, then1.4], then2, catch1]

выглядит так:

new Promise(...)
.then(() => {         // <- 1
  return Promise(...)
          .then()     // <- 1.1
          .then()     // <- 1.2
          .catch()    // <- 1.1
          .then()     // <- 1.3
          .then()     // <- 1.4
})
.then()               // <- 2
.catch()              // <- 1

Теперь представьте, что мы запускаем выполнение, и оно начинается в самом верхнем массиве. У нас есть индекс для каждого массива, определяющий, какой элемент мы ожидаем с точки зрения выполнения. Мы начинаем с вызова .then1, который сам возвращает цепочку обещаний (другой массив). Когда возникает ошибка, ваш самый младший массив иерархии (самый глубокий) пропускает (не выполняет) его элементы до тех пор, пока не найдет catch. Если это так, он выполняет catch и продолжает выполнение других элементов. Если он не находит catch он просит родительский массив найти и выполнить catch, пропуская все элементы, включая его дочерние массивы, потому что они не пойманы.

В нашем примере, если в then1.2 возникает then1.2 он будет пойман catch1.1, но если он произойдет в then1.3 он будет распространять весь путь до catch1 пропустив then1.4 а then2

Редактировать:

Вот код для экспериментов с:

new Promise(res => res())
.then(() => 
    new Promise(res => res())
    .then(() => console.log(1))
    .then(() => {console.log(2); throw "Error 1"})
    .catch((err) => console.log(err))
    .then(() => {console.log(3); throw "Error 2"}))
    .then(() => console.log(4))
.then(() => 
    new Promise(res => res())
    .then(() => console.log(6))
    .then(() => {console.log(7); throw "Error 3"})
    .catch((err) => console.log(err))
    .then(() => console.log(8))
    .then(() => {console.log(9); throw "Error 4"}))
.then(() => console.log(10))
.catch((err) => console.log(err))

он регистрирует:

1

2

Ошибка 1

3

Ошибка 2

Ответ 2

Поэтому сначала сделайте шаг в своем примере.

const p = new Promise(resolve => resolve(5))
 .then(v => 5*v)
 .then(v => { throw 'foo' });

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('first catch:', e));

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('second catch:', e));

Здесь у вас есть 3 разных обещания:

p который является Promise.then.then, тогда anonymous который является p.then.then.catch, и anonymous который является p.then.then.catch.

Помните, что Promise.then и Promise.catch возвращают Обещания, которые охватывают Обещание ранее. Так что вы действительно заканчиваете с вложенными обещаниями с этими тремя целями обещания.

Когда p конечном итоге бросает третье обещание при оценке, другие два обещания теперь проверяют тип возвращаемого значения, флаги и другую внутреннюю информацию, что в конечном итоге приводит к тому, что они реализуют обещание от ранее отказавшейся цепи и, следовательно, оно также должно отклоняться. Вы также можете подражать этому:

var p = Promise.reject(new Error('Hi from p!'))

p.catch(e => console.log('Hello from a different promise', p))
==
p.catch(e => console.log('Hello from a yet different promise', p))

Вы должны заметить, что возвращение оценки ложно, что означает, что два объекта не равны.

Теперь, что произойдет, если мы немного подождем, а затем присоедините новый обработчик улова к p?

setTimeout(() => p.catch(console.log), 500)

Вы должны заметить другой журнал консоли, на этот раз только "Привет от p!".

И причина - порядок, по которому оценивается обещание. Он оценивает создание и, если он создан по отклоненному обещанию, он оценивает отклонение и переходит к обработчику уловов.

Как вы думаете, что получится со следующим?

Promise
  .reject(new Error('1'))
  .catch(console.log)
  .then(() => Promise.reject(new Error('2')))
  .then(() => new Error('3'))
  .then(console.log)
  .catch(console.log)
  .catch(() => console.log('4'))

Правильно вы увидите напечатанную ошибку ("1"), затем напечатанную ошибку ("2"), но не напечатанную "Ошибка" ("3") или напечатанную "4". Это потому, что обещание .catch делает одно из двух: разрешает обещание разрешенному значению или решает обещание значения функций, если цепь отклоняется.

В отличие от .then только решает обещание. Если разрешение отклонено, оно отклоняется с разрешением.

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