предисловие
оPromise
На Наггетс уже есть много отличных статей по принципиальному анализу. Но автор всегда вЕсли ты это прочитаешь, то потеряешь, как только напишешьЭто цель автора, написавшего эту статью, чтобы понятьPromise
Идея написания состоит в том, чтобы написать волну кода с нуля, и вам также будет удобно пересматривать его в будущем.
Роль обещаний
Promise
даJavaScript
Популярное решение для асинхронного программирования, оно появилось для решенияад обратного звонкаЭто позволяет пользователям писать асинхронный код с помощью цепного метода записи.Автор не будет вводить конкретное использование.Вы можете обратиться к работе г-на Руана Ифэна.Учебное пособие по промисам ES6.
Предварительные знания
Шаблон наблюдателя
Что такое шаблон наблюдателя:
Шаблон наблюдателя определяет отношение зависимости «один ко многим», позволяя нескольким объектам-наблюдателям одновременно отслеживать целевой объект.При изменении состояния целевого объекта все объекты-наблюдатели будут уведомлены, чтобы их можно было обновить автоматически.
Promise
основывается наШаблон проектирования наблюдателяосуществленный,then
Функция, которая будет выполняться функцией, будет помещена в массив наблюдателя, когдаPromise
При изменении состояния выполняются все функции массива группы наблюдения.
механизм цикла событий
выполнитьPromise
вовлеченныйJavaScript
механизм цикла событий вEventLoop
, а также концепции макрозадач и микрозадач.
Блок-схема механизма цикла событий выглядит следующим образом:
Вы можете посмотреть на этот код:
console.log(1);
setTimeout(() => {
console.log(2);
},0);
let a = new Promise((resolve) => {
console.log(3);
resolve();
}).then(() => {
console.log(4);
}).then(() => {
console.log(5);
});
console.log(6);
Если вы не можете сразу сказать результат вывода, я предлагаю вам сначала проверить его.цикл событийДля соответствующей информации есть много отличных статей в Nuggets.
Обещания/спецификация A+
Promises/A+является нормой сообщества, если вы хотите написать нормативнуюPromise
, мы должны следовать этому стандарту. После этого мы также улучшим свои собственные согласно спецификации.Promise
.
Обещайте основные очки знаний
писать от рукиPromise
Перед этим давайте пройдемся по нескольким важным пунктам знаний.
executor
// 创建 Promise 对象 x1
// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
// 业务逻辑处理成功结果
const value = ...;
resolve(value);
// 失败结果
// const reason = ...;
// reject(reason);
}
let x1 = new Promise(executor);
первыйPromise
это класс, который получает функцию выполненияexecutor
, который принимает два параметра:resolve
а такжеreject
Эти два параметраPromise
Две функции, определенные внутри, используются для изменения состояния и выполнения соответствующей функции обратного вызова.
потому что
Promise
Он не знает, был ли результат выполнения неудачным или успешным. Он просто предоставляет контейнер для асинхронных операций. Фактический контроль находится в руках пользователя. Пользователь может вызвать два вышеуказанных параметра, чтобы сообщитьPromise
Является ли результат успешным, и при этом бизнес-логика обрабатывает результат (value/reason
) в качестве параметраresolve
а такжеreject
Две функции, которые выполняют обратный вызов.
три состояния
Promise
Есть три состояния:
-
pending
:Ожидающий -
resolved
: удалось -
rejected
: не удалось
существуетPromise
Есть только два возможных изменения состояния: отpending
сталиresolved
или изpending
сталиrejected
, как показано ниже (цитата из мини-книги Promise):
И следует отметить, что после изменения состояния оно больше не изменится, и результат всегда будет одним и тем же. То есть, когда мыexecutor
функция называетсяresolve
после, после звонкаreject
не влияет, и наоборот.
// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
resolve(100);
// 之后调用 resolve,reject 都是无效的,
// 因为状态已经变为 resolved,不会再改变了
reject(100);
}
let x1 = new Promise(executor);
then
Каждыйpromise
все одноthen
метод, это когдаpromise
После возврата результата функция обратного вызова, которую необходимо выполнить, имеет два необязательных параметра:
-
onFulfilled
: успешный обратный вызов; -
onRejected
: неудачный обратный вызов;
Как показано ниже (цитата из мини-книги Promise):
// ...
let x1 = new Promise(executor);
// x1 延迟绑定回调函数 onResolve
function onResolved(value){
console.log(value);
}
// x1 延迟绑定回调函数 onRejected
function onRejected(reason){
console.log(reason);
}
x1.then(onResolved, onRejected);
Рукописное обещание общего процесса
Здесь мы просто пишем один вручнуюPromise
Общий процесс:
исполнитель с тремя состояниями
-
new Promise
, вам нужно пройтиexecutor
функция-исполнитель в конструкторе,Функция-исполнитель выполняется немедленно -
executor
Функция execute принимает два параметра:resolve
а такжеreject
-
Promise
только отpending
прибытьrejected
, или изpending
прибытьfulfilled
-
Promise
Как только состояние подтверждено, оно замораживается и не изменяется.
затем метод
- все
Promise
имеютthen
метод,then
Получает два параметра, которыеPromise
успешный обратный вызовonFulfilled
, и неудачный обратный вызовonRejected
- если звонишь
then
час,Promise
удалось, выполнитьonFulfilled
, и воляPromise
Значение передается как параметр; еслиPromise
не удалось, затем выполнитеonRejected
, и воляPromise
Причина сбоя передается как параметр; еслиPromise
Статусpending
, нужно бытьonFulfilled
а такжеonRejected
Функция сохраняется, ожидает определения состояния, а затем по очереди выполняет соответствующую функцию (режим наблюдателя) -
then
параметрыonFulfilled
а такжеonRejected
Вы не можете передать это,Promise
Проникновение стоимости возможно.
Цепные вызовы и обработка, а затем возвращаемые значения
-
Promise
Могуthen
неоднократно,Promise
изthen
метод возвращает новыйPromise
. - если
then
возвращает нормальное значение, то этот результат (value
) в качестве параметра, переданного следующемуthen
Успешный обратный вызов (onFulfilled
) - если
then
выдает исключение, то это исключение (reason
) в качестве параметра, переданного следующемуthen
неудачный обратный вызов (onRejected
) - если
then
возвращаетpromise
или другойthenable
объекта, то вам нужно дождаться этогоpromise
закончить исполнение,promise
В случае успеха переходите к следующемуthen
обратный вызов успеха; если это не удается, перейти к следующемуthen
обратный вызов с ошибкой.
Вышеизложенное является общим процессом реализации.Неважно, если вы запутались, пока у вас есть общее впечатление, мы поговорим о них по порядку позже.
Затем мы начнем реализовывать простейший пример, чтобы начать объяснение.
Первое издание (начиная с простого примера)
Давайте сначала напишем простую версию.Эта версия не поддерживает вызовы состояний и цепочек, а поддерживает только вызов одного
then
метод.
приходи 🌰
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolved('成功了');
}, 1000);
})
p1.then((data) => {
console.log(data);
}, (err) => {
console.log(err);
})
Простой пример1s
вернуться после成功了
, И вthen
на выходе.
выполнить
мы определяемMyPromise
class, а затем мы пишем в нем код, конкретный код выглядит следующим образом:
class MyPromise {
// ts 接口定义 ...
constructor (executor: executor) {
// 用于保存 resolve 的值
this.value = null;
// 用于保存 reject 的值
this.reason = null;
// 用于保存 then 的成功回调
this.onFulfilled = null;
// 用于保存 then 的失败回调
this.onRejected = null;
// executor 的 resolve 参数
// 用于改变状态 并执行 then 中的成功回调
let resolve = value => {
this.value = value;
this.onFulfilled && this.onFulfilled(this.value);
}
// executor 的 reject 参数
// 用于改变状态 并执行 then 中的失败回调
let reject = reason => {
this.reason = reason;
this.onRejected && this.onRejected(this.reason);
}
// 执行 executor 函数
// 将我们上面定义的两个函数作为参数 传入
// 有可能在 执行 executor 函数的时候会出错,所以需要 try catch 一下
try {
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
// 定义 then 函数
// 并且将 then 中的参数复制给 this.onFulfilled 和 this.onRejected
private then(onFulfilled, onRejected) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
}
Что ж, наша первая версия завершена, не правда ли, она очень проста.
Однако здесь следует отметить, что
resolve
Время выполнения функции должно быть вthen
После того, как метод зарегистрирует функцию обратного вызова, вresolve
После этого при переходе к callback-функции присваивания она фактически завершается и не имеет смысла.Приведенный выше пример хорош, потому что
resolve(成功了)
завернут вsetTimeout
, он будет выполнен в следующей задаче макроса, когда функция обратного вызова будет зарегистрирована.Каждый может попробовать
resolve(成功了)
отsetTimeout
Убери его, в это время будут проблемы.
Существует проблема
Эта версия очень проста в реализации, но есть еще несколько проблем:
- Понятие государства не введено.
Понятие состояния не введено, и теперь состояние может быть изменено по желанию, что не соответствуетPromise
Правило, согласно которому состояния могут изменяться только из состояний ожидания.
- Сцепленные вызовы не поддерживаются
Обычно мы можемPromise
Выполнение цепочек вызовов:
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolved('成功了');
}, 1000);
})
p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
- Поддерживается только одна функция обратного вызова. Если функций обратного вызова несколько, последняя перезапишет первую.
В этом примереonResolved2
будет охватыватьonResolved1
,onRejected2
будет охватыватьonRejected1
.
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolved('成功了');
}, 1000);
})
// 注册多个回调函数
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);
Далее мы идем еще дальше и решаем эти проблемы.
Второе издание (реализация связанных вызовов)
В этой версии мы вводим понятие состояния и одновременно реализуем функцию цепного вызова.
плюс государство
Выше мы сказалиPromise
Есть три состояния:pending
,resovled
,rejected
, только изpending
Перевести вresovled
илиrejected
, и когда состояние изменяется, состояние не может быть изменено.
- Мы определяем свойство
status
: используется для записи текущегоPromise
положение дел - Во избежание ошибок определим состояние как константу
PENDING
,RESOLVED
,REJECTED
. - При этом мы сэкономим
then
Обратный вызов успеха определяется как массив:this.resolvedQueues
а такжеthis.rejectedQueues
, мы можем положитьthen
Функции обратного вызова вставляются в соответствующий массив, что может решить третью проблему, о которой мы упоминали выше.
class MyPromise {
private static PENDING = 'pending';
private static RESOLVED = 'resolved';
private static REJECTED = 'rejected';
constructor (executor: executor) {
this.status = MyPromise.PENDING;
// ...
// 用于保存 then 的成功回调数组
this.resolvedQueues = [];
// 用于保存 then 的失败回调数组
this.rejectedQueues = [];
let resolve = value => {
// 当状态是 pending 是,将 promise 的状态改为成功态
// 同时遍历执行 成功回调数组中的函数,将 value 传入
if (this.status == MyPromise.PENDING) {
this.value = value;
this.status = MyPromise.RESOLVED;
this.resolvedQueues.forEach(cb => cb(this.value))
}
}
let reject = reason => {
// 当状态是 pending 是,将 promise 的状态改为失败态
// 同时遍历执行 失败回调数组中的函数,将 reason 传入
if (this.status == MyPromise.PENDING) {
this.reason = reason;
this.status = MyPromise.REJECTED;
this.rejectedQueues.forEach(cb => cb(this.reason))
}
}
try {
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
}
Совершенствуйте функцию then
Потом будем улучшатьthen
метод в , прежде чем мы непосредственноthen
два параметраonFulfilled
а такжеonRejected
, непосредственно назначенныйPromise
Свойства экземпляра, используемые для сохранения успешных и неудачных обратных вызовов функций.
Теперь нам нужно запихнуть эти два свойства в два массива:resolvedQueues
а такжеrejectedQueues
.
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
// 当参数不是函数类型时,需要创建一个函数赋值给对应的参数
// 这也就实现了 透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}
// 当状态是等待态的时候,需要将两个参数塞入到对应的回调数组中
// 当状态改变之后,在执行回调函数中的函数
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push(onFulfilled)
this.rejectedQueues.push(onRejected)
}
// 状态是成功态,直接就调用 onFulfilled 函数
if (this.status === MyPromise.RESOLVED) {
onFulfilled(this.value)
}
// 状态是成功态,直接就调用 onRejected 函数
if (this.status === MyPromise.REJECTED) {
onRejected(this.reason)
}
}
}
Некоторые примечания о функции then
- при каких обстоятельствах
this.status
будетpending
статус, при каких обстоятельствах это будетresolved
условие
На самом деле это связано с механизмом цикла событий, следующий код:
// this.status 为 pending 状态
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0)
}).then(value => {
console.log(value)
})
// this.status 为 resolved 状态
new MyPromise((resolve, reject) => {
resolve(1)
}).then(value => {
console.log(value)
})
- чтопроникнуть
Как код ниже, когдаthen
Когда никакие параметры не передаются,Promise
Будет использовать внутренний метод, определенный по умолчанию, с передачей результата следующемуthen
.
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolved('成功了');
}, 1000);
})
p1.then().then((res) => {
console.log(res);
})
Поскольку мы пока не поддерживаем цепочку, этот код столкнется с проблемами.
Поддержка связанных вызовов
Поддержка цепочек вызовов на самом деле очень проста, нам нужно только датьthen
Функция, наконец, возвращаетthis
Вот и все, поэтому цепные вызовы поддерживаются:
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// ...
return this;
}
}
каждый звонокthen
После этого мы все возвращаем текущий thisPromise
объект, потому чтоPromise
объект существуетthen
метод, на этот раз мы просто реализуемPromise
простой звонок.
В это время запустите вышеуказанноепроникнутьтестовый код.
Однако приведенный выше код по-прежнему имеет соответствующие проблемы, см. следующий код:
const p1 = new MyPromise((resolved, rejected) => {
resolved('resolved');
});
p1.then((res) => {
console.log(res);
return 'then1';
})
.then((res) => {
console.log(res);
return 'then2';
})
.then((res) => {
console.log(res);
return 'then3';
})
// 预测输出:resolved -> then1 -> then2
// 实际输出:resolved -> resolved -> resolved
Результат отличается от наших ожиданий, потому что мыthen
вернулся вthis
представляет собойp1
,существуетnew MyPromise
После этого фактически состояние изменилось сpending
изменился наresolved
состояние, после этого оно не изменится, поэтому вMyPromise
серединаthis.value
значение всегда былоresolved
.
В этот момент мы должны посмотреть наthen
Очки знаний о возвращаемых значениях.
затем вернуть значение
Фактическиthen
вернет новыйPromise
объект.
Взгляните на следующий код:
// 新创建一个 promise
const aPromise = new Promise(function (resolve) {
resolve(100);
});
// then 返回的 promise
var thenPromise = aPromise.then(function (value) {
console.log(value);
});
console.log(aPromise !== thenPromise); // => true
Из приведенного выше кода мы можем получитьthen
метод возвращенPromise
уже не оригиналPromise
, как показано ниже (цитата из мини-книги Promise):
promise
последовательные вызовы, за которыми следуетjQuery
Цепочка звонков разная,jQuery
Объект, возвращаемый цепным вызовом, по-прежнему является исходным.jQuery
объект;Promise
Некоторые методы больше похожи на массив, напримерslice
, возвращает новое значение после каждой операции.
Код модернизации
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
// then 方法返回一个新的 promise
const promise2 = new MyPromise((resolve, reject) => {
// 成功状态,直接 resolve
if (this.status === MyPromise.RESOLVED) {
// 将 onFulfilled 函数的返回值,resolve 出去
let x = onFulfilled(this.value);
resolve(x);
}
// 失败状态,直接 reject
if (this.status === MyPromise.REJECTED) {
// 将 onRejected 函数的返回值,reject 出去
let x = onRejected(this.reason)
reject && reject(x);
}
// 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push((value) => {
let x = onFulfilled(value);
resolve(x);
})
this.rejectedQueues.push((reason) => {
let x = onRejected(reason);
reject && reject(x);
})
}
});
return promise2;
}
}
// 输出结果 resolved -> then1 -> then2
Существует проблема
Здесь мы выполнили простой связанный вызов, но поддерживаются только синхронные связанные вызовы.then
Если в методе выполняются другие асинхронные операции, приведенный выше код будет GG.
Следующий код:
const p1 = new MyPromise((resolved, rejected) => {
resolved('我 resolved 了');
});
p1.then((res) => {
console.log(res);
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
resolved('then1');
}, 1000)
});
})
.then((res) => {
console.log(res);
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
resolved('then2');
}, 1000)
});
})
.then((res) => {
console.log(res);
return 'then3';
})
Приведенный выше код будет напрямую конвертироватьPromise
Объект передается непосредственно в качестве параметра следующемуthen
функция, и мы на самом деле хотим преобразовать этоPromise
Результат обработки передается дальше.
Третье издание (асинхронная цепочка)
В этой версии мы реализуем
promise
асинхронная цепочка вызовов.
идеи
Первый взглядthen
серединаonFulfilled
а такжеonRejected
Возвращаемое значение:
// 成功的函数返回
let x = onFulfilled(this.value);
// 失败的函数返回
let x = onRejected(this.reason);
Как видно из поставленного выше вопроса,x
может бытьобщее значение, также может бытьPromise
Объект, передача обычных значений, в котором мы находимсявторое изданиеЭто было решено, теперь нужно решить, когдаx
вернутьPromise
как обращаться с предметами.
Это на самом деле очень просто, когдаx
ЯвляетсяPromise
Когда объект является временем, нам нужно дождаться возвратаPromise
При изменении статуса, после выполненияthen
функция, код выглядит следующим образом:
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}
// then 方法返回一个新的 promise
const promise2 = new MyPromise((resolve, reject) => {
// 成功状态,直接 resolve
if (this.status === MyPromise.RESOLVED) {
// 将 onFulfilled 函数的返回值,resolve 出去
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}
// 失败状态,直接 reject
if (this.status === MyPromise.REJECTED) {
// 将 onRejected 函数的返回值,reject 出去
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
}
// 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.rejectedQueues.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
})
}
});
return promise2;
}
}
Пишем новую функциюresolvePromise
, эта функция является основным методом, используемым для обработки асинхронных цепных вызовов, он оценитx
Возвращаемое значениеPromise
объект, если так, покаPromise
После успешного возврата состояние изменяется.Если это нормальное значение, это значение напрямуюresovle
выйди:
const resolvePromise = (promise2, x, resolve, reject) => {
if (x instanceof MyPromise) {
const then = x.then;
if (x.status == MyPromise.PENDING) {
then.call(x, y => {
resolvePromise(promise2, y, resolve, reject);
}, err => {
reject(err);
})
} else {
x.then(resolve, reject);
}
} else {
resolve(x);
}
}
описание кода
resolvePromise
resolvePromise
Принимает четыре параметра:
-
promise2
даthen
вернулся вpromise
; -
x
даthen
два параметраonFulfilled
илиonRejected
Возвращаемое значение типа неопределенно, это может быть обычное значение, это может бытьthenable
объект; -
resolve
а такжеreject
даpromise2
из.
затем вернуть тип значения
когдаx
даPromise
, а его состояниеPending
статус, еслиx
Если выполнение прошло успешно, то переходим к рекурсивному вызовуresolvePromise
Эта функция будетx
результат выполнения какresolvePromise
Передается второй параметр;
Если выполнение не удается, вызовите напрямуюpromise2
изreject
метод.
Здесь мы в основном имеем полныйpromise
, то нам нужноPromises/A+стандартизировать нашиPromise
.
Канонические обещания
Автор предыдущих редакций кода в основном следовал спецификации, вот некоторые моменты, которые не соответствуют спецификации.
Спецификация затем (Spec 2.2)
then
серединаonFulfilled
а такжеonRejected
Его нужно выполнять асинхронно, то есть поместить в асинхронную задачу для выполнения (спецификация 2.2.4)
выполнить
мы должныthen
функция черезsetTimeout
Заверните его и поместите в задачу макроса, которая здесь задействованаjs
изEventLoop
, вы можете перейти к соответствующим статьям следующим образом:
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// ...
// then 方法返回一个新的 promise
const promise2 = new MyPromise((resolve, reject) => {
// 成功状态,直接 resolve
if (this.status === MyPromise.RESOLVED) {
// 将 onFulfilled 函数的返回值,resolve 出去
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
})
}
// 失败状态,直接 reject
if (this.status === MyPromise.REJECTED) {
// 将 onRejected 函数的返回值,reject 出去
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
})
}
// 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
})
})
this.rejectedQueues.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
})
})
}
});
return promise2;
}
}
Используйте посылки Micro Cass
Но есть еще проблема, мы знаемPromise.then
это микрозадача, теперь при использованииsetTimeout
После упаковки это эквивалентно тому, чтобы стать задачей макроса.Вы можете увидеть следующий пример:
var p1 = new MyPromise((resolved, rejected) => {
resolved('resolved');
})
setTimeout(() => {
console.log('---setTimeout---');
}, 0);
p1.then(res => {
console.log('---then---');
})
// 正常 Promise:then -> setTimeout
// 我们的 Promise:setTimeout -> then
Порядок вывода не тот, причина в том, что текущийPromise
черезsetTimeout
Задача макроса завернута.
Мы можем улучшить это немного и использовать микрозаски для обертыванияonFulfilled
,onRejected
, часто используемые микрозадачиprocess.nextTick
,MutationObserver
,postMessage
подождите, мы используем этоpostMessage
Перепишите это:
// ...
if (this.status === MyPromise.RESOLVED) {
// 将 onFulfilled 函数的返回值,resolve 出去
// 注册一个 message 事件
window.addEventListener('message', event => {
const { type, data } = event.data;
if (type === '__promise') {
try {
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
}
});
// 立马执行
window.postMessage({
type: '__promise',
}, "http://localhost:3001");
}
// ...
Метод реализации очень прост, следимwindow
изmessage
событие, и сразу после этого запускаетpostMessage
событие, на этот разthen
Функция обратного вызова уже находится в очереди микрозадачи, давайте перезапустим пример, и вы увидите, что порядок вывода изменилсяthen -> setTimeout
.
Конечно
Promise
Внутренняя реализация определенно не так проста, здесь автор только дает идею, если интересно, можете изучить.
Спецификация функции resolvePromise (спецификация 2.3)
повторные цитаты
повторные цитаты, когда
x
а такжеpromise2
Если совпадает, то нужно сообщить об ошибке и повторить заявку. (Спецификация 2.3.1)
Потому что ожидание завершения себя никогда не бывает плодотворным.
const p1 = new MyPromise((resolved, rejected) => {
resolved('我 resolved 了');
});
const p2 = p1.then((res) => {
return p2;
});
тип х
Примерно делится на следующие:
- 2.3.2: когда
x
ЯвляетсяPromise
, затем подождитеx
После изменения состояния оно считается выполненным или неудавшимся (это также относится к2.3.3
,потому чтоPromise
На самом делеthenable
объект) - 2.3.3: Когда
x
является объектом или функцией, т.е.thenable
объект, то этоx.then
так какthen
- 2.3.4: Когда
x
не является объектом или функцией, непосредственноx
как параметрresolve
вернуть.
Мы в основном смотрим2.3.3
просто отлично, потому чтоPrmise
также принадлежатthenable
объект, что такоеthenable
Что с объектом?
Проще говоря, он имеетthenобъект/функция метода, все
Promise
объектыthenable
объекты, но не всеthenable
объект неPromise
объект. следующим образом:let thenable = { then: function(resolve, reject) { resolve(100); } }
согласно сx
Тип для обработки:
-
если
x
нетthenable
объект, звоните напрямуюPromise2
изresolve
,Будуx
в результате успеха; -
когда
x
даthenable
объект, который вызоветx
изthen
метод, вызов после успехаresolvePromise
функция и выполнит результатy
как новыйx
входящийresolvePromise
, до этогоx
ценность больше не являетсяthenable
объект; в случае сбоя он будет вызван напрямуюpromise2
изreject
.
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
if (typeof then === 'function') {
then.call(x, (y) => {
resolvePromise(promise2, y, resolve, reject);
}, (err) => {
reject(err);
})
}
} else {
resolve(x);
}
позвонить только один раз
Технические характеристики(
Promise/A+ 2.3.3.3.3
) указывает, что если оба вызоваresolvePromise
а такжеrejectPromise
или выполняется несколько вызовов с одним и тем же параметром, первый вызов имеет приоритет, а все остальные вызовы игнорируются, что гарантирует выполнение только одного изменения состояния.
Мы определяемcalled
заполнитель, чтобы получитьthen
Независимо от того, выполнила ли функция соответствующую функцию, которая изменяет состояние, после выполнения она больше не будет выполняться, в основном для соответствия спецификации.
x является объектом обещания
еслиx
даPromise
объект, на самом деле, когда выполняетсяresolve
После функции она больше не будет выполнятьсяreject
функция, она находится непосредственно в текущейPromise
Объект заканчивается.
x - это объект, который можно использовать
когдаx
обычныйthenable
функцию, можно выполнять одновременноresolve
а такжеreject
функция, которая может выполняться одновременноpromise2
изresolve
функция иreject
функцию, а на самом делеpromise2
После изменения состояния соответствующее значение не изменится. На самом деле проблемы нет, следующий код:
// thenable 对像
{
then: function(resolve, reject) {
setTimeout(() => {
resolve('我是thenable对像的 resolve');
reject('我是thenable对像的 reject')
})
}
}
Полная решимостьОбещание
полныйresolvePromise
Функция выглядит следующим образом:
const resolvePromise = (promise2, x, resolve, reject) => {
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
Вы сделали здесь, счастливы вы или нет!
Наконец мы можем запустить наш тестовый скриптMyPromise
соответствовать спецификации.
тестовое задание
Есть специальные тестовые скрипты (promises-aplus-tests
) может помочь нам проверить, соответствует ли код, который мы пишем,Promise/A+
Технические характеристики.
Но, кажется, только тест
js
файл, так что автор будетts
файл конвертируется вjs
файл, тест
Добавьте в код:
// 执行测试用例需要用到的代码
MyPromise.deferred = function() {
let defer = {};
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
Вам необходимо заранее установить тестовый плагин:
# 安装测试脚本
npm i -g promises-aplus-tests
# 开始测试
promises-aplus-tests MyPromise.js
Результат выглядит следующим образом:
Прошло отлично, дальше видноPromise
Реализовано больше методов.
больше способов
реализовать вышеуказанноеPromise
После этого относительно просто написать его экземпляр и статические методы.
метод экземпляра
Promise.prototype.catch
выполнить
По сути, этот методthen
Синтаксический сахар для методов, просто дайтеthen
передачаonRejected
параметрok
.
private catch(onRejected) {
return this.then(null, onRejected);
}
пример:
const p1 = new MyPromise((resolved, rejected) => {
resolved('resolved');
})
p1.then((res) => {
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
rejected('错误了');
}, 1000)
});
})
.then((res) => {
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
resolved('then2');
}, 1000)
});
})
.then((res) => {
return 'then3';
}).catch(error => {
console.log('----error', error);
})
// 1s 之后输出:----error 错误了
Promise.prototype.finally
выполнить
finally()
метод используется, чтобы указать, является лиPromise
Операция будет выполнена независимо от конечного состояния объекта.
private finally (fn) {
return this.then(fn, fn);
}
пример
const p1 = new MyPromise((resolved, rejected) => {
resolved('resolved');
})
p1.then((res) => {
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
rejected('错误了');
}, 1000)
});
})
.then((res) => {
return new MyPromise((resolved, rejected) => {
setTimeout(() => {
resolved('then2');
}, 1000)
});
})
.then((res) => {
return 'then3';
}).catch(error => {
console.log('---error', error);
return `catch-${error}`
}).finally(res => {
console.log('---finally---', res);
})
// 输出结果:---error 错误了" -> ""---finally--- catch-错误了
статический метод
Promise.resolve
выполнить
Иногда необходимо преобразовать существующие объекты вPromise
объект,Promise.resolve()
метод делает именно это.
static resolve = (val) => {
return new MyPromise((resolve,reject) => {
resolve(val);
});
}
пример
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => {
console.log(res);
}).catch((error) => {
console.log(error);
});
// 输出结果:{name: "darrell", sex: "boy"}
Promise.reject
выполнить
Promise.reject(reason)
метод также возвращает новыйPromise
экземпляр, состояние экземпляраrejected
.
static reject = (val) => {
return new MyPromise((resolve,reject) => {
reject(val)
});
}
пример
MyPromise.reject("出错了").then((res) => {
console.log(res);
}).catch((error) => {
console.log(error);
});
// 输出结果:出错了
Promise.all
Promise.all()
метод объединения несколькихPromise
экземпляр, завернутый в новыйPromise
пример,
const p = Promise.all([p1, p2, p3]);
- Только
p1
,p2
,p3
статус сталfulfilled
,p
государство станетfulfilled
; - если только
p1
,p2
,p3
один из них былrejected
,p
состояние становитсяrejected
, в это время первыйreject
Возвращаемое значение экземпляра , будет передано вp
функция обратного вызова.
выполнить
static all = (promises: MyPromise[]) => {
return new MyPromise((resolve, reject) => {
let result: MyPromise[] = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, error => {
reject(error);
});
}
});
}
пример
let Promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1');
}, 2000);
});
let Promise2 = new MyPromise((resolve, reject) => {
resolve('Promise2');
});
let Promise3 = new MyPromise((resolve, reject) => {
resolve('Promise3');
})
let Promise4 = new MyPromise((resolve, reject) => {
reject('Promise4');
})
let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);
p.then((res) => {
// 三个都成功则成功
console.log('---成功了', res);
}).catch((error) => {
// 只要有失败,则失败
console.log('---失败了', err);
});
// 直接输出:---失败了 Promise4
Promise.race
Promise.race()
Этот метод также заключается в переносе нескольких экземпляров Promise в новый экземпляр Promise.
const p = Promise.race([p1, p2, p3]);
если толькоp1
,p2
,p3
Один из экземпляров первым меняет состояние,p
статус меняется соответственно. Первый, кто изменилPromise
Возвращаемое значение экземпляра передается вp
функция обратного вызова.
выполнить
static race = (promises) => {
return new Promise((resolve,reject)=>{
for(let i = 0; i < promises.length; i++){
promises[i].then(resolve,reject)
};
})
}
пример
пример иall
Аналогично, вызов выглядит следующим образом:
// ...
let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])
p.then((res) => {
console.log('---成功了', res);
}).catch((error) => {
console.log('---失败了', err);
});
// 直接输出:---成功了 Promise2
Promise.allSettled
Этот метод принимает группуPromise
экземпляр как параметр, завернутый в новыйPromise
пример.
const p = Promise.race([p1, p2, p3]);
Просто подождите, пока все эти экземпляры параметров вернут результаты, либоfulfilled
ещеrejected
, и состояние метода может стать толькоfulfilled
.
Этот метод
Promise.all
Разница в том,all
Невозможно определить, что все запросы завершились, потому что вall
, если одинPromise
одеялоrejected
,p
состояние сразу становитсяrejected
, возможно, некоторые асинхронные запросы не были завершены.
выполнить
static allSettled = (promises: MyPromise[]) => {
return new MyPromise((resolve) => {
let result: MyPromise[] = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].finally(res => {
result[i] = res;
if (++count == promises.length) {
resolve(result);
}
})
}
});
}
пример
пример иall
Аналогично, вызов выглядит следующим образом:
let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])
p.then((res) => {
// 三个都成功则成功
console.log('---成功了', res);
}, err => {
// 只要有失败,则失败
console.log('---失败了', err);
})
// 2s 后输出:---成功了 (4) ["Promise1", "Promise2", "Promise3", "Promise4"]
Суммировать
В этой статье автор шаг за шагом проведет вас к достижениюPromise/A+
нормативныйPromise
После прочтения я считаю, что каждый может в принципе написатьPromise
приходящий.
Наконец, ответив на несколько вопросов, вы сможете увидеть, насколько вы освоили его:
-
Promise
Как реализовать проникновение возвращаемого значения функции обратного вызова? -
Promise
После ошибки, как вы прошлипузырьПерейти к последней функции, которая ловит исключение? -
Promise
Как поддерживать связанные вызовы? - как будет
Promise.then
Превратить это в микрозадачу?
Честно говоря, я хочу комплимент!
Справочная документация
- Учебное пособие Ruan Yifeng ES6 Promise
- обещанная мини-книга
- Документ спецификации Promises/A+
- Проанализируйте внутреннюю структуру Promise, шаг за шагом, чтобы реализовать полный класс Promise, который может пройти все тестовые случаи.
- 30 минут, чтобы вы полностью поняли принцип Promise
- Вручную реализовать промис, который удовлетворяет промис-аплюс-тестам
- Интервьюер: Пожалуйста, опишите одним предложением, какие исключения JS можно перехватывать с помощью try catch.
образец кода
Пример кода можно найти здесь: