Как обеименные цепочки ссылаются на следующий обработчик ошибок
Мне интересно, знает ли кто-нибудь, как цепочки обещаний ссылаются на следующий обработчик ошибок - например:
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
только решает обещание. Если разрешение отклонено, оно отклоняется с разрешением.
Это немного сбивает с толку да, но как только вы поймете, что обещание будет ждать разрешения, и будет проверять и связывать обещания, становится немного легче понять, куда вы откажетесь.