Заявление Моюцзяна в статье: Гарантируется оригинальность содержания, распространяются и передаются только технические галантерейные товары, никакой рекламы и хвастовства.
Предисловие: эта статья должна отличаться от большинства рукописных потенциалов, которые вы видели. Это не расскажет о спецификации / A +, и не упомянет синтаксический сахар, такой как Promess.race.race / Race. В этой статье я буду использовать объектно-ориентированный образ мышления многое и сосредоточиться только на основной идее обещания и ее реализации. Я считаю, что после того, как вы внимательно прочитаете, у вас будет более структурное понимание обещания Отказ
Ладно, хватит разговоров, давайте к делу. Для Promise MDN объясняет это так: объект Promise используется для представления окончательного завершения (или отказа) асинхронной операции и ее значения результата. В этой статье я беру за отправную точку идею объектно-ориентированного программирования и даю ей другое определение:Объект Promise — это контейнер, который можно использовать для хранения состояния и данных, полученных в результате асинхронных операций..
Вокруг этой концепции я использовал диаграмму классов UML для организации объекта Promise, чтобы вы могли лучше понять концепцию контейнера, о котором я говорю.Схема выглядит следующим образом:
Фактически, приведенная выше диаграмма классов может использоваться только для описанияСинхронная работаСтруктура объекта-контейнера для состояния результата и данных. Поскольку фактический объект-контейнер Promise должен поддерживать управлениеАсинхронная работаПолученное состояние и данные, поэтому его диаграмма классов должна добавить два внутренних свойства для временного хранения функций обратного вызова (причина будет объяснена позже), в это время его диаграмма классов будет выглядеть так:
Все обсуждение и реализация Обещаний ниже будут вращаться вокруг этой картинки.Если вам интересно продолжить чтение, я надеюсь, что вы сможете потратить некоторое время на понимание этой картинки, чтобы лучше прочитать следующее.
Хорошо, тогда я буду использовать этапы программной реализации объектно-ориентированного программирования в качестве идеи и разделю ее на следующие части для достижения цели рукописного промиса:
- Используя концепцию контейнера в качестве точки входа, реализуйте базовую структуру объекта Promise.
- Проанализируйте взаимосвязь между контейнером Promise и асинхронными операциями и реализуйте конструктор конструктора Promise.
- Уточните, как записываются данные в контейнер промиса, и реализуйте методы разрешения и отклонения промиса.
- Уточните, как считываются данные в контейнере Promise, и реализуйте метод then для Promise.
- Добавьте требование к методу then, поддержите вызовы цепочек и облегчите обработку потоков асинхронных операций.
Столкнувшись с письменным сценарием тестового собеседования, который требует написанных от руки обещаний, люди также будут следовать приведенной выше картинке и следовать приведенным выше идеям и шагам для ее достижения.
1: Спроектируйте и реализуйте базовую структуру объекта Promise с концепцией контейнера в качестве точки входа.
Прежде чем анализировать ответ, я надеюсь, что вы можете заранее подумать над следующими двумя вопросами, основываясь на картинке выше:
- Какие данные находятся в контейнере Promise?
- Как читать и записывать данные в контейнер Promise?
Раньше я называл операции чтения и записи контейнера здесь заполнение и выборка. Позже, учитывая, что данные в контейнере Promise поддерживают однократное заполнение и выборку бесчисленное количество раз, точно так же, как данные диска, я буду называть эти операции позже для чтения и пишу.
1. Данные, хранящиеся в контейнере (свойства объекта)
- состояние: состояние контейнера, которое делится на три типа, а именно состояние контейнера не готово к рассмотрению и состояние контейнера готово выполнено, отклонено
- значение: данные в заполненном состоянии контейнера
- причина: данные в контейнере отклонены
- onResolvedTodoList: массив поведений обратного вызова в состоянии выполнения контейнера, временно сохраненный в состоянии ожидания и используемый после состояния выполнения.
- onRejectedTodoList: массив поведения обратного вызова после того, как контейнер находится в отклоненном состоянии, временно сохраняется в состоянии ожидания и используется после отклоненного состояния.
Чтобы сделать имена переменных более единообразными и знакомыми, в следующей реализации промиса, написанной от руки, мы заменяем состояние выполнено на состояние разрешения (просто измените имя, не нужно запутываться).
2. Чтение и запись данных в контейнер (объектный метод)
- ввод данных: Контейнер внутри определяет и предоставляет методы разрешения и отклонения, которые могут устанавливать внутреннее состояние и данные для внешних вызовов для записи данных (состояние, значение/причина) для контейнера.
- читать данные: Контейнер определяет и предоставляет метод then, который может считывать внутреннее состояние и данные для внешних вызовов для чтения данных в контейнере и запускать соответствующее поведение обратного вызова в соответствии с состоянием контейнера.
3. Реализация объектного кода контейнера
Поняв состав инкапсуляции (атрибуты, методы) объекта Promise, в соответствии с приведенной выше диаграммой классов UML, мы делаем следующую реализацию кода:
Напоминание: убедитесь, что вы понимаете следующую структуру кода, это общая картина Promise, мы фокусируемся на общей картине.
class Container {
state = undefined;
value = undefined;
reason = undefined;
onResolvedTodoList = [];
onRejectedTodoList = [];
constructor(excutor) { // 构造容器 }
resolve = value => { // 写容器数据 }
reject = reason => { // 写容器数据 }
then(onResolved, onRejected) { // 读容器数据 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
Чтобы подчеркнуть концепцию контейнеров, я дал имя класса Container классу Promise, который мы собираемся написать вручную.
Второй: анализ взаимосвязи между контейнером Promise и асинхронными операциями и реализация конструктора Promise.
Прежде чем конкретно реализовать конструктор Promise, давайте проанализируем взаимосвязь между волной контейнеров Promise и асинхронными операциями:
- Контейнер обещаний: это контейнер асинхронных операций, который хранит данные и управляет их чтением и записью.
- Асинхронная операция: комбинация некоторых синхронных и асинхронных операторов — это наша реальная логика кода.
- Связь между контейнерами Promise и асинхронными операциями: асинхронные операции принимают результаты своего выполнениядоверитьПозвольте объекту-контейнеру Promise управлять им.
Слово commit очень хорошо отражает связь между асинхронными операциями и контейнером Promise.
1. Понять связь между асинхронными операциями и промисами из слова доверенный
На диаграмме классов UML мы описали структуру объекта Promise, и теперь мыСосредоточившись на действии доверительного управления, полностью поймите связь между асинхронными операциями и контейнерами Promise.:
- Установить доверительные отношения: внешняя готовность, внешний выбор контейнера Promise для управления результатами асинхронных операций. (ps: до ES6 функция обратного вызова была шаттлом).
- Доверие данных контейнеру: внешняя воля, внешние вызовы разрешают и отклоняют методы в асинхронных операциях, чтобы решить, какое состояние и данные доверить контейнеру. Следует отметить, что мы часто доверяем данные обратному вызову асинхронного оператора.
- Запрос данных в контейнере: внешняя готовность, внешнее решение, когда запрашивать данные и что делать после запроса данных. Следует отметить, что когда данные в контейнере считываются извне, доверенные данные часто являются асинхронными, поэтому легко заставить данные контейнера читаться и записываться.проблема временного расстройства.
Исходя из требований, согласно идее использования объектов-контейнеров для управления результатами асинхронных операций, можно спроектировать свой собственный промис, и сто человек напишут сотни видов промисов.
хорошо, получаюВнешне доверить результаты асинхронных операций контейнеру Promise для управленияПосле этого основного понимания мы можем понять логику дизайна официального Обещания. Далее реализуем конструктор методов конструирования официальной версии контейнера Promise, который включает в себя два процесса асинхронной работы и установление доверительных отношений Promise и процесс доверительного отношения данных к контейнеру.
Что же касается того, почему официальная версия Promise устроена именно так, то это определяется спросом.Контейнер будет создан только тогда, когда результат асинхронной операции будет управляться контейнером.(Ваш продукт, ваш товар), поэтому он помещает эти два процесса в метод построения (конечно, наше собственное обещание может быть не таким, но может достичь цели, но я считаю, что из пакета Легко смотреть на это не так дружелюбно, как официальная версия).
2. Реализовать конструктор Promise
(1): Пример реализации кода
class Container {
state = undefined;
value = undefined;
reason = undefined;
onResolvedTodoList = [];
onRejectedTodoList = [];
// 接收excutor函数作为构造参数并立即调用,根据excutor函数形参约定进行实参传递。
constructor(excutor) {
try {
excutor(this.resolve, this.reject);
this.state = Container.PENDING;
} catch (e) {
this.reject(e)
}
}
resolve = value => { // 写容器数据 }
reject = reason => { // 写容器数据 }
then(onResolved, onRejected) { // 读容器数据 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
(2): Пример вызова конструкции
// 外部定义excutor函数,约定形参resolve和reject
const p1 = new Container((resolve, reject) => {
// 外部决定什么时候写入容器数据,写入什么数据
setTimeout(() => {
resolve(0)
})
})
(3): Идея функционального программирования видится исполнителю
В соответствии с приведенным выше анализом метода построения официальной версии реализации необходимо установить асинхронную операцию и доверительные отношения перед контейнером Promise и доверять данные контейнеру.Судя по двум обязанностям, функция excutor имеет две функции:
- Носитель асинхронной операции: форма функционального объекта
- Разрешить внешним параметрам управлять данными и состоянием в контейнере: разрешать и отклонять методы записи данных контейнера.
В соответствии с функциональными обязанностями проанализируйте ввод и вывод функции, чтобы углубить понимание и впечатление:
- Ввод функции: методы записи данных в контейнер, а именно разрешение и отклонение
- Вывод функции: форма побочного эффекта, запись данных контейнера
- Логика сопоставления: во время выполнения асинхронной операции записывать данные в контейнер в соответствии с потребностями вызывающей стороны.
Третье: уточнить способ записи данных в контейнер Promise и реализовать методы разрешения и отклонения Promise.
Так называемая запись данных контейнера по сути означает, что методы в контейнере присваивают значения данным в контейнере. Если это простое задание, то нет смысла обсуждать, но в официальной версии Promise есть несколько деталей, на которые нужно обратить внимание.Давайте посмотрим на пример реализации официальной версии Promise:
1. Пример реализации и пример вызова
Официальная версия промиса делит данные контейнера на взаимоисключающие данные значения и причины и соответствует состоянию готовности выполнено и отклонено, если мы проектируем сами, то может быть не так.
class Container {
// ...attr
constructor(excutor) {}
resolve = value => {
if (this.state != Container.PENDING) return
this.status = Container.RESOLVED;
this.value = value;
while (this.onResolvedTodoList.length) this.onResolvedTodoList.shift()() // 取出第一个
}
reject = reason => {
if (this.state != Container.PENDING) return
this.status = Container.REJECTED;
this.reason = reason;
while (this.onRejectedTodoList.length) this.onRejectedTodoList.shift()()
}
then(onResolved, onRejected) {}
}
После инкапсуляции давайте посмотрим на пример вызова:
const p1 = new Promise((resolve, reject) => {
// 同步方式往容器中写入数据
// resolve(0)
// 异步方式往容器中写入数据
setTimeout(() => {
resolve(1)
})
})
2. Вопрос: Почему состояние non-pending ничего не делает при записи данных?
Асинхронные данные, управляемые контейнером Promise, — это данные результата после операции.После завершения операции данные не должны изменяться. Это соответствует его строгому именованию Promise означает обещание Только данные после операции не меняются, внешний мир может доверять и доверять данным в этом контейнере.
Это также означает, что когда мы ежедневно используем Promise, мы можем вызвать метод разрешения и метод отклонения только один раз, а когда мы вызываем его несколько раз, допустим только первый раз.
3. Вопрос: Почему мне нужно потреблять временную очередь обратного вызова после записи данных?
Временная очередь обратного вызова потребления сохраняет добавленное извне поведение обратного вызова в контейнер, когда контейнер находится в состоянии ожидания. Во многих случаях, когда внешний вызов вызывает метод then для чтения состояния и данных в контейнере для выполнения каких-либо действий, состояние и данные контейнера не были записаны из-за асинхронных причин, поэтому контейнер не может немедленно ответить на внешний запрос. обратный вызов после чтения. Но Promise надежен, он обещает нам, что как только его собственное состояние и данные будут установлены, он сразу же сделает то, что мы для него устроили, и ничего не отстанет.
4. Вопрос: Почему методы разрешения и отклонения должны быть установлены как стрелочные функции?
Некоторые друзья однажды задали мне этот вопрос, и он спросил, почему функции разрешения и отклонения должны быть установлены как стрелочные функции, а не объявления функций. На самом деле, это неплохая идея объявить ее как функцию Причина, по которой она установлена как стрелочная функция, заключается в том, что функция, объявленная в форме стрелочной функции, не может быть изменена вызывающей стороной каким-либо образом, когда она То же самое верно и для функции связывания.
Следовательно, установка стрелочной функции здесь — это просто внешнее ограничение, так что внешнее не может изменить точку этого в функциях разрешения и отклонения, вынуждая соглашение о том, что метод разрешения и метод отклонения могут использоваться только для записи данные текущего объекта-контейнера.
Четвертое: Уточните, как читаются данные в контейнере Promise, и реализуйте метод then для Promise.
Так называемое чтение данных в контейнере Promise, согласно нашему общепринятому пониманию, на самом деле заключается в определении метода get для возврата внутренних данных контейнера. Также из названия метода then видно, что это не так просто, как получить данные, его китайское значение — then, что и должна делать следующая операция асинхронной операции. Давайте сначала напишем простейший метод then, чтобы углубить наше понимание концепции метода then:
const obj = {
value: 1,
then: function (fn) {
fn(this.value)
}
}
obj.then(console.log);
Друзья, изучавшие функторы, могут сравнить разницу между тогдашним методом Promise и методом отображения функторов. Подумайте о том же контейнере, почему функторы подходят для потока преобразования данных, а Promise подходит для потока асинхронных операций.
Друзья, думающие об этом, могут сказать, что параметры callback-функции в then-функции promise выполняются в виде микрозадач. Я рад, что вы задали этот вопрос.Да, метод then в приведенном выше примере явно выполняется синхронно и не соответствует требованиям метода then обещания.Переделаем его так, чтобы его параметры callback-функции были в виде микрозадач Выполняем, код такой:
const obj = {
value: 1,
then: function (fn) {
process.nextTick(() => {
fn(this.value);
});
},
};
obj.then(console.log);
Напоминание: процесс — это API в среде узла, и его нельзя использовать в среде bom!
Ладно, дальше фуфло, давайте рассмотрим на примере, как реализовать метод then, имитирующий официальную версию Promise, когда не поддерживаются вызовы цепочки (поддержка пятой точки).
1. Пример реализации и пример вызова
Без необходимости поддержки цепочек вызовов функция then и ее реализация очень просты: она отвечает за получение двух параметров обратного вызова, заданных пользователем, а затем данные, хранящиеся в контейнере, решат, какой вызывать в соответствии с состоянием функции. контейнер после того, как он будет готов. Функция обратного вызова, и вызвать функцию с данными, хранящимися в контейнере, в качестве параметра. Прежде чем реализовать функцию, давайте проанализируем метод then с идеями функционального программирования.
- затем ввод функции: функция обратного вызова onResolved/onRejected, которая получает данные значения/причины в качестве параметра.
- Вывод функции then: в виде побочных эффектов вызов callback-функции с параметром.
- Логика сопоставления функций then: определить, готов ли контейнер, временно сохранить функцию параметра обратного вызова, когда он не готов, и решить, какую функцию обратного вызова выполнить в соответствии с состоянием, когда он будет готов.
Нижеследующее настолько просто и так коротко, чтоПример реализации функции then, которую можно понять с первого взглядаи пример вызова:
Пример реализации:
class Container {
state = undefined;
value = undefined;
reason = undefined;
onResolvedTodoList = [];
onRejectedTodoList = [];
constructor(excutor) {}
resolve = value = >{}
reject = reason = >{}
then(onResolved, onRejected) {
// 问题:为什么对参数onResolved和onRejected做缺省处理?
onResolved = onResolved ? onResolved: value => value;
onRejected = onRejected ? onRejected: reason => { // 问题:为什么onRejected回调缺省处理逻辑不是reason => reason?
throw reason
};
switch (this.state) {
// 问题:为什么需要判断pending状态,,这个状态下的代码逻辑为什么是暂存回调函数而不是调用回调函数?
case Container.PENDING:
// 问题:为什么将回调函数onResolved和onRejected放入暂存队列中时用箭头函数包裹后传入而不是直接传入onResolved或者onRejected?
this.onResolvedTodoList.push(() = >{
// 问题:then函数的回调函数参数不是以微任务形式调用吗,为什么你这里写成宏任务的形式呢?
setTimeout(() => {
// 问题:为什么回调函数的调用不做异常处理?
onResolved(this.value);
});
});
this.onRejectedTodoList.push(() = >{
setTimeout(() => {
onRejected(this.reason);
});
});
break;
case Container.RESOLVED:
setTimeout(() => {
onResolved(this.value);
});
break;
case Container.REJECTED:
setTimeout(() => {
onRejected(this.reason);
});
break;
}
}
}
Пример вызова:
p1.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
Ниже мы обсудим несколько основных логик и проблем в коде.
2. Вопрос: Почему параметры onResolved и onRejected обрабатываются по умолчанию?
Лично я думаю, что это в основном связано со следующими двумя аспектами:
- Избегайте большого количества логики нулевых оценок при вызове функций обратного вызова onResolved и onRejected.
- При вызове в цепочке он поддерживает проникновение в состояние результата и данные без передачи функции обратного вызова или без передачи ее вообще. (Причина будет объяснена в пункте 5)
3. Вопрос: Почему логика обработки по умолчанию обратного вызова onRejected не причина => причина?
Имеет смысл, что логика обработки по умолчанию для обратного вызова onRejected разработана как Reason => {причина броска}, Лично я думаю, что это в основном зависит от спроса. Поскольку контейнер промисов используется для управления результатом операции, а операция часто означает успех или неудачу, мы часто используем состояние разрешения как успех для чтения и записи данных в контейнере, а состояние отклонения — как отказ чтения и записи. данные в контейнере. В то же время, когда мы выполняем логику кода синхронной или асинхронной операции, иногда нам не нужно обращать внимание на значение после ее успеха, но мы должны знать о неудаче операции, потому что это означает, что доверенная ей операция может не выполняться. быть успешным и не производить побочных эффектов, которых мы хотим.
Состояние причины и состояние отклонения не обязательно представляют состояния успеха или отказа, а соответствующие данные не обязательно представляют данные успеха или данные отказа. Промис — это просто контейнер, в котором значение состояния и управляемых данных всегда определяется вызывающей стороной.
4. Вопрос. Зачем нужно оценивать состояние ожидания и почему логика кода в этом состоянии временно сохраняет функцию обратного вызова, а не вызывает функцию обратного вызова?
Цель конструкции обещания с отложенным состоянием — решить проблему путаницы во времени чтения и записи данных контейнера, вызванную асинхронными операциями. Мы можем назвать это состояние ожидания тем, что контейнер был создан, но не готов. В это время состояние в контейнере находится в состоянии ожидания, а данные не определены. Когда снаружи вызывается метод then контейнера обещания для выполнения следующей операции, он не может получить контейнер, состояние и данные предыдущей операции сохраняются, поэтому их нельзя вызвать.
Надежно то, что, хотя его нельзя вызвать немедленно, промисы не будут считать внешние функции обратного вызова, добавленные в это время, недействительными, они будут временно хранить эти функции обратного вызова, а затем ожидать использования, когда контейнер будет готов. Обход массива onResolvedTodoList и массива onResolvedTodoList в методе reject вызывается по порядку.
5. Вопрос. Почему функции обратного вызова onResolved и onRejected помещаются во временную очередь, обернутую стрелочными функциями, и передаются вместо прямой передачи в onResolved или onRejected?
Прежде всего, уточним требования: когда обход временной очереди вызывает callback-функцию, она должна нести значение или причину в качестве параметра для вызова, то есть выполнение callback-функции с параметрами. Что касается того, почему он не передается при прохождении вызова в функции разрешения или отклонения? Я не думаю, что есть что-то плохое в том, чтобы сделать это здесь.
Если у вас есть разные мнения по этому вопросу, сообщите нам об этом в комментариях, спасибо!
В частности, следует ли обернуть еще один слой структуры замыкания стрелочными функциями, я думаю, это можно рассмотреть с учетом следующих трех аспектов:
- Когда вы хотите, чтобы эта функция обратного вызова выполнялась?
- В какой среде вы хотите, чтобы эта функция обратного вызова выполнялась?
- На что вы хотите, чтобы это в обратном вызове указывало? (Функция, на которую это указывает, тесно связана с вызовом функции)
6. Вопрос: Параметр callback-функции then-функции не вызывается в виде микрозадачи, почему вы тут написали в виде макрозадачи?
Я рад, что вы задали этот вопрос. Вы правы. Согласно официальной версии обещания, мы действительно не должны использовать setTimeout для регистрации в качестве задачи макроса для выполнения параметров функции обратного вызова метода then. Но это бесполезно.Если мы хотим, чтобы наше рукописное обещание использовалось в среде bom, то мы не можем использовать метод process.nextTick, и мы не можем найти подходящее api микрозадачи, которое можно использовать в среде bom, поэтому это и В примерах я записал их в виде задач макроса (промисы, используемые в среде написанного вручную узла, не имеют этой проблемы). Это не большая проблема.Вы можете использовать setTimeout для рукописных обещаний.Когда интервьюер спрашивает, почему, вы можете ответить, почему.
Некоторые друзья могут также спросить, почему официальная версия promise может это сделать, параметры callback-функции ее then-метода вызываются в виде микрозадач. Нет никакого способа сделать это.Обещания людей — это встроенные классы (также называемые объектами, в зависимости от того, как вы их понимаете), они не написаны на JavaScript, и, естественно, они не ограничены JavaScript и не созданы.
Если эта статья не сможет обмануть ваши лайки, я уйду из Nuggets o( ̄ヘ ̄o#)!
7. Вопрос: Почему вызов callback-функции не обрабатывает исключения?
У некоторых друзей может возникнуть эта проблема после прочтения реализации цепного метода then. Я пытаюсь сказать, что мы не занимаемся обработкой исключений просто так. Делайте это только тогда, когда есть реальная ценность в обработке исключений. Есть ли разница между выполнением и невыполнением обработки исключений здесь? Это то же самое, чтобы поймать исключение здесь или глобально перехватить вызов этой функции обратного вызова. Нам не нужно сообщать следующей операции, когда встречается исключение, подобное цепочке, и возникает проблема со следующей операцией, поэтому следующая операция должна перейти к функции обратного вызова ошибки.
Если у вас есть какие-либо идеи по этому поводу, пожалуйста, дайте мне знать, спасибо!
Пятое: добавление требования к методу then, поддержка цепочек вызовов и упрощение обработки потоков асинхронных операций.
Величайшее очарование обещания в том, что оноАсинхронный поток операцийРеализация формы вложенного кодирования обратного вызова меняется на текущую тогда форму кодирования «синхронного вызова», что полностью решает проблему кодирования ада обратного вызова, которая мучила разработчиков интерфейса в течение многих лет.
Здесь я сначала упомяну, что я имею в виду под потоком асинхронных операций.Поток операций можно понимать как операция -> следующая операция -> следующая операция -> следующая следующая операция -> ... такой процесс, асинхронная операция Поток означает, что некоторая операции в приведенном выше потоке операций имеют асинхронную логику. Образно говоря, мы сначала собираем производственный цех несколькими методами, а затем (ваш продукт, ваш прекрасный продукт).
Функция вызова цепочки then позволяет обещаниям поддерживать обработку задач асинхронной операции.Если вы понимаете вызов цепочки then с точки зрения требований, я предлагаю вам всегда понимать пять слов потока асинхронной операции.
Если бы мне нужно было дать несколько ярлыков для понимания промисов, я бы назвал их тремя: контейнеры, асинхронность и поток операций.
Итак, как это реализовать?Для цепного вызова then просто подумайте об этом своей задницей и знайте, что вы можете вернуть новый объект-контейнер обещания в методе then. Однако здесь необходимо прояснить несколько ключевых моментов.Давайте сначала рассмотрим пример реализации и пример вызова связанного метода then.
1. Цепочка, затем пример реализации метода и пример вызова
Напоминание: рекомендуется сравнивать пример реализации метода then, который не вызывается в цепочке, в процессе сравнения выяснять изменения метода then, чтобы реализовать вызов в цепочке.
class Container {
constructor(excutor) {}
resolve = value => {}
reject = reason => {}
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {
onResolved = onResolved ? onResolved : value => value;
onRejected = onRejected ? onRejected : reason => { throw reason };
let containerBack = new Container((resolve, reject) => {
switch (this.state) {
case Container.PENDING:
this.onResolvedTodoList.push(() => {
setTimeout(() => {
// 问题:为什么这里又开始异常处理了呢?
try {
const value = onResolved(this.value);
// 问题:为什么不直接把新的value作为新容器管理的数据,而是封装一个resolveContainer函数?
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
});
this.onRejectedTodoList.push(() => {
setTimeout(function () {
try {
const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
});
break;
case Container.RESOLVED:
setTimeout(() => {
try {
const value = onResolved(this.value);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
break;
case Container.REJECTED:
setTimeout(function () {
try {
const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
break;
}
});
return containerBack
}
}
function resolveContainer(containerBack, value, resolve, reject) {
if (!(value instanceof Container)) {
resolve(value)
} else {
if (value !== containerBack) {
value.then(resolve, reject);
} else {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
}
}
Пример вызова:
const p2 = p1.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
p2.then((value) => {
console.log('p2', value)
}, (reason) => {
console.log('p2', reason)
}).then((value) => {
console.log('p3', value)
}, (reason) => {
console.log('p3', reason)
})
2. Вопрос: Почему здесь снова запускается обработка исключений?
Это потому, что в случае связанных вызовов мы можем думать о внутренней операции функции обратного вызова как о основной части асинхронной операции, размещенной в следующем промисе (в следующем пункте мы поговорим об особых случаях), если эта новая асинхронная операция не удалась. , Мы думаем, что состояние нового промиса — отклонение, а данные причины — это объект исключения. Почему бы просто не захватить его и не позволить внешнему сообщению об ошибке? Это связано с тем, что в случае связанных вызовов пользователи могут относиться к состоянию отклонения и данным как к некоему состоянию, а промисы не имеют права прерывать (с точки зрения спроса — пробуешь, пробуешь внимательно).
3. Вопрос. Почему бы не использовать новое значение напрямую в качестве данных, управляемых новым контейнером, а вместо этого инкапсулировать функцию resolveContainer?
Давайте посмотрим непосредственно на функцию resolveContainer:
function resolveContainer(containerBack, value, resolve, reject) {
if (!(value instanceof Container)) { // 回调函数返回一个非promise容器对象,这时候回调函数基本上等同于外部构造新容器时传入该回调函数作为异步操作
resolve(value)
} else {
if (value !== containerBack) {
value.then(resolve, reject); // 回调函数返回一个promise容器对象,非自身,新的promise接管这个回调函数返回的promise容器对象的状态和数据
} else { // 回调函数返回一个promise容器对象,并且是新容器自身,这时新的promise接管新的promise容器对象的状态和数据(无限套娃)
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
}
}
Возможно, лучше найти ответ непосредственно из приведенного выше кода и комментариев. Почему так получилось? Я так понимаю, с точки зрения спроса, мы не можемПросто относитесь к функции обратного вызова как к основной части асинхронной операции нового промиса., Это понимание очень важно.Многие люди просто не знают, что такое тело асинхронной операции, управляемое новым промисом, и какова связь между ним и параметрами функции обратного вызова функции then (пробуй, пробуй внимательно) , а затем привести к незнанию, создает ли этот новый объект-контейнер обещаний.
Любой вопрос? Вопросы приветствуются в комментариях. Если вы можете указать на мою проблему и дать мне возможность исправить ее, я был бы более признателен!