Поскольку автор переходит на TypeScript, TypeScript по-прежнему будет использоваться для этой разработки.
Это должен быть последний раз, когда я делюсь статьей с заголовком TypeScript, до свидания 🤞, я могу смело отправляться в путь. (Я столько раз кричал, садись в автобус быстро, но в автобус влезло мало людей, поэтому я выйду первым.)
Эта статья предназначена для нулевого понимания читателем повторного изучения Promise, а знания можно получить следующим образом:
-
Promise важныйТочка знаний
-
Метод реализации Promise API
-
Как передняя часть чувствует себя в безопасности?
Автор надеется, что читатели смогуттолько эта статьяВы можете иметь глубокое понимание промисов,И вы можете сами реализовать класс Promise. Поэтому я буду рассказывать о Promise со всех сторон, а содержания может быть и больше.Рекомендуется читателям на выбор.
Зачем внедрять обещания
Promise появился в Es6, если Es5 нужно использовать Promise, то его обычно нужно использоватьPromise-polyfill
. То есть то, что мы хотим реализовать, — это полифилл. Его реализация не только помогает нам получить представление о промисах, но и снижает вероятность ошибок при использовании, что приводит к лучшим практикам промисов.
С другой точки зрения, повторная реализация зависимости также является способом интерпретации исходного кода, и настаивать на этом означает улучшить способность интерпретировать исходный код.
Promise
Промис представляет собой конечный результат асинхронной операции, и основным способом взаимодействия с ним является метод then, который регистрирует две callback-функции для получения конечного значения промиса или причины, по которой промис не может быть выполнен.
Давайте взглянем на схему структуры API, нарисованную автором (если вам не ясно, вы можете ввести моюGitHubСмотри, там большие картинки и исходники xmind ):
Картинка выше простоСхема API для промисов,фактическиPromises/A+
Спецификация не определяет, как создаются, разрешаются и отклоняются промисы, а вместо этого фокусируется на предоставлении общего метода then. Таким образом, реализации спецификации Promise/A+ могут прекрасно сосуществовать с менее формальными, но пригодными для использования реализациями. Если все будут следовать норме, проблем с совместимостью будет не так много. (PS: включая веб-стандарты) Давайте поговоримPromises/A+
, можете пропустить, если видели.
Promises/A+
Все реализации Promise неотделимы отPromises/A+Технические характеристики, содержания не много, предлагаю пройти. Вот несколько важных моментов в спецификации
период, термин
-
Promise
надоthen
объект или функция методов, поведение которых соответствуетPromises/A+
Технические характеристики; -
thenable
один определяетthen
Объект или функция метода также можно рассматривать как «принадлежащийthen
метод" -
值(value)
Ссылаться наЛюбыеJavaScriptюридическая ценность(включая undefined , thenable и promise) -
异常(exception)
использоватьthrow
значение, выброшенное оператором -
据因(reason)
Указывает причину отклонения обещания.
Состояние обещаний
текущее состояние промисадолженЭто одно из следующих трех состояний: Ожидание, Выполнено и Отклонено.
-
等待态(Pending)
В состоянии ожидания обещание должно удовлетворять:
可以
Переход в состояние выполнения или отклонения -
执行态(Fulfilled)
В состоянии выполнения обещание должно удовлетворять:
不能
Для миграции в любой другой штат необходимо иметь不可变
из终值
-
拒绝态(Rejected)
В состоянии отклонения обещание должно удовлетворять:
不能
Для миграции в любой другой штат необходимо иметь不可变
из据因
Неизменяемый здесь относится к идентичности (т.е.
===
равенства), а не подразумевает более глубокую неизменность (имеется в виду, когда ценность или причина небазовая стоимость, должны совпадать только его ссылочные адреса, но значение свойства может быть изменено).
Затем метод
Обещание должно предоставлять метод then для доступа к его текущему значению, конечному значению и причине.
Метод then обещания принимает два параметра:
promise.then(onFulfilled, onRejected);
-
onFulfilled
а такжеonRejected
являются необязательными параметрами. -
если
onFulfilled
это функция, когда обещаниевыполнение заканчиваетсяпосле чего он должен быть вызван, которыйпервый параметрза обещаниеконечное значение, прежде чем обещание завершит выполнениене подлежит отзыву, который нельзя вызывать более одного раза -
если
onRejected
это функция, когда обещание вызываетсяотклонятьОн должен вызываться после выполнения, егопервый параметрза обещаниеСогласно с, прежде чем обещание будет отклоненоне подлежит отзыву, который нельзя вызывать более одного раза -
onFulfilled
а такжеonRejected
Он доступен для вызова только в том случае, если стек среды выполнения содержит только код платформы (ссылка на движок, среду и обещание реализации кода) -
На практике обеспечить
onFulfilled
а такжеonRejected
метод выполняется асинхронно и должен бытьthen
Выполняется в новом стеке выполнения после цикла обработки событий, в котором был вызван метод. -
onFulfilled
а такжеonRejected
Должна вызываться как функция без этого значения (то есть в строгом режиме функция this не определена, в нестрогом режиме это глобальный объект). -
Тогда метод можно назвать несколько раз по тому же обещанию
-
тогда метод должен возвращать обещанный объект
Затем параметр (функция) возвращает значение
Я надеюсь, что читатели смогут внимательно прочитать эту часть, чтобы понять обещаниеthen
метод очень помогает.
Давайте посмотрим на процесс выполнения обещания:
Общий процесс заключается в том, что обещание начнется сpending
Перевести вfulfilled
илиrejected
, а затем вызовите соответственноthen
параметр методаonFulfilled
илиonRejected
, который в итоге возвращаетpromise
объект.
Для дальнейшего понимания предположим, что есть следующие два обещания:
promise2 = promise1.then(onFulfilled, onRejected);
Будут следующие ситуации:
-
если
onFulfilled
илиonRejected
генерировать исключение e, то обещание2должен отказаться от исполнения, и вернуться拒因 e
-
если
onFulfilled
не функцияи обещание1 выполняется успешно, обещание2 должно выполниться успешно и вернутьто же значение -
если
onRejected
не функцияИ обещание1 отказывается выполняться, обещание2 должно отказаться от выполнения и вернутьсята же причина
Если вы хотите узнать больше, вы можете скопировать следующий код в консоль Chrome или другую исполняемую среду, чтобы испытать его:
// 通过改变 isResolve 来切换 promise1 的状态
const isResolve = true;
const promise1 = new Promise((resolve, reject) => {
if (isResolve) {
resolve('promise1 执行态');
} else {
reject('promise1 拒绝态');
}
});
// 一、promise1 处于 resolve 以及 onFulfilled 抛出异常 的情况
// promise2 必须拒绝执行,并返回拒因
promise1
.then(() => {
throw '抛出异常!';
})
.then(
value => {
console.log(value);
},
reason => {
console.log(reason);
}
);
// 二、promise1 处于 resolve 以及 onFulfilled 不是函数的情况
// promise2 必须成功执行并返回相同的值
promise1.then().then(value => {
console.log(value);
});
// 三、promise1 处于 reject 以及 onRejected 不是函数的情况
// promise2 必须拒绝执行并返回拒因
promise1.then().then(
() => {},
reason => {
console.log(reason);
}
);
// 四、promise1 处于 resolve 以及 onFulfilled 有返回值时
promise1
.then(value => {
return value;
})
.then(value => {
console.log(value);
});
Ниже приведена еще одна важная ситуация, связанная с проблемой переноса стоимости в бизнес-сценариях:
-
onFulfilled
илиonRejected
вернутьдопустимое значение JavaScriptСлучай
Давайте сначала предположим, что внутри метода then есть метод с именем[[Resolve]]
Этот метод используется для работы с этим особым случаем, и ниже приводится подробное описание этого метода.
[[Resolve]]
метод
вообще нравится[[...]]
Такое представление реализуется внутренне, например[[Resolve]]
, который принимает два параметра:
[[Resolve]](promise, x);
дляx
значение в следующих случаях:
-
x
имеютзатем методи выглядит какPromise
-
x
как объект или функция -
x
дляPromise
Также обещание не может быть равно x, т.е.promise !== x
,иначе:
Взгляните на картинку ниже, чтобы получить общее представление о том, как действовать в каждой ситуации:
Promise/A+
резюме
Слишком далеко,Promise/A+
Это то, что вам нужно знать. В основном включает терминологию и использование метода Then и соответствующие меры предосторожности. Особое внимание следует уделить обработке возвращаемого значения параметра в методе then. Затем мы используем TypeScript для реализации Promise на основе спецификации.
Далее, приведенные в тексте спецификации относятся конкретно кPromise/A+
Технические характеристики
Обещание реализации
Promise сам по себе является конструктором, т.е. может быть реализован как класс. Затем основное внимание уделяется реализации класса Promise.
Давайте взглянем на свойства и методы следующего стандартного объекта обещания, чтобы вы знали, чего ожидать.
API, предоставляемые Promises:
Внутренние свойства обещания включают в себя:
Приступим к формальной части реализации
Файл декларации
Прежде чем мы начнем, давайте взглянем на некоторые объявления типов, используемые при написании промисов в TypeScript. могу видеть этоФайл декларации.
в основном включает:
Типприъемный заявленный файл для внешнего модуля является основной частью TypeyctScript. Кроме того, может быть использовано оператор из API файла обзора, в котором может быть выставлен модуль (принять, какой тип или какой тип данных будет возвращен). Эта API разработана заранее, чтобы разработать хорошую привычку, но фактическое развитие было бы сложнее.
Далее, давайте посмотрим на начало базовой реализации класса Promise — конструктора.
Конструктор
В спецификации упоминается, что конструктор Promise принимаетResolver
Функция типа в качестве первого параметра принимает два параметра разрешения и отклонения, используемых для обработки состояния обещания.
Реализация выглядит следующим образом:
class Promise {
// 内部属性
private ['[[PromiseStatus]]']: PromiseStatus = 'pending';
private ['[[PromiseValue]]']: any = undefined;
subscribes: any[] = [];
constructor(resolver: Resolver<R>) {
this[PROMISE_ID] = id++;
// resolver 必须为函数
typeof resolver !== 'function' && resolverError();
// 使用 Promise 构造函数,需要用 new 操作符
this instanceof Promise ? this.init(resolver) : constructorError();
}
private init(resolver: Resolver<R>) {
try {
// 传入两个参数并获取用户传入的终值或拒因。
resolver(
value => {
this.mockResolve(value);
},
reason => {
this.mockReject(reason);
}
);
} catch (e) {
this.mockReject(e);
}
return null;
}
private mockResolve() {
// TODO
}
private mockReject() {
// TODO
}
}
[[Решить]] реализация
Из предыдущего раздела спецификации мы узнали, что[[Resolve]]
Принадлежит к внутренней реализации для обработки возвращаемой стоимости параметра затем. То есть, что собирается быть реализованным здесь, называетсяmockResolve
Методы.
По спецификации известно, чтоmockResolve
Значение, принимаемое методом, может быть Promise, thenable и другими допустимыми значениями JavaScript.
private mockResolve(value: any) {
// 规范提到 resolve 不能传入当前返回的 promise
// 即 `[[Resolve]](promise,x)` 中 promise !== x
if (value === this) {
this.mockReject(resolveSelfError);
return;
}
// 非对象和函数,直接处理
if (!isObjectORFunction(value)) {
this.fulfill(value);
return;
}
// 处理一些像 promise 的对象或函数,即 thenable
this.handleLikeThenable(value, this.getThen(value));
}
Обработка объектов Thenable
Сфокусируйся наhandleLikeThenable
Реализация, сочетание нескольких случаев, упомянутых спецификацией спереди для анализа:
private handleLikeThenable(value: any, then: any) {
// 处理 "真实" promise 对象
if (this.isThenable(value, then)) {
this.handleOwnThenable(value);
return;
}
// 获取 then 值失败且抛出异常,则以此异常为拒因 reject promise
if (then === TRY_CATCH_ERROR) {
this.mockReject(TRY_CATCH_ERROR.error);
TRY_CATCH_ERROR.error = null;
return;
}
// 如果 then 是函数,则检验 then 方法的合法性
if (isFunction(then)) {
this.handleForeignThenable(value, then);
return;
}
// 非 Thenable ,则将该终植直接交由 fulfill 处理
this.fulfill(value);
}
Обработать случай, когда Then в Thenable является функцией
В спецификации упоминается:
Если then это функция, вызовите ее с x в качестве области видимости функции this . Передайте две функции обратного вызова в качестве параметров, первый параметр называется resolvePromise, а второй параметр называется rejectPromise.
В настоящее время,handleForeignThenable
Он используется для проверки метода then.
Реализация выглядит следующим образом:
private tryThen(then, thenable, resolvePromise, rejectPromise) {
try {
then.call(thenable, resolvePromise, rejectPromise);
} catch (e) {
return e;
}
}
private handleForeignThenable(thenable: any, then: any) {
this.asap(() => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 此处 sealed (稳定否),用于处理上诉逻辑
let sealed = false;
const error = this.tryThen(
then,
thenable,
value => {
if (sealed) {
return;
}
sealed = true;
if (thenable !== value) {
this.mockResolve(value);
} else {
this.fulfill(value);
}
},
reason => {
if (sealed) {
return;
}
sealed = true;
this.mockReject(reason);
}
);
if (!sealed && error) {
sealed = true;
this.mockReject(error);
}
});
}
выполнить выполнение
приходите посмотреть[[Resolve]]
последний шаг,fulfill
выполнить:
private fulfill(value: any) {
this['[[PromiseStatus]]'] = 'fulfilled';
this['[[PromiseValue]]'] = value;
// 用于处理异步情况
if (this.subscribes.length !== 0) {
this.asap(this.publish);
}
}
Увидев это, вы, возможно, заметили, что многие методы имеютprivate
модификаторы в TypeScript
Свойство или метод, оформленный в частном порядке, является закрытым и не может быть доступен за пределами класса, в котором он объявлен.
В спецификации упоминается, чтоPromiseStatus
Свойства нельзя изменить снаружи, то есть состояние промиса можно изменить только один раз, и только изнутри, то есть приватный метод здесьfulfill
обязанности.
[[Resolve]]
резюме
Пока что внутренний[[Resolve]]
Это сбылось. Давайте рассмотрим,[[Resolve]]
Используется для обработки следующих ситуаций
// 实例化构造函数,传入 resolve 的情况
const promise = Promise(resolve => {
const value: any;
resolve(value);
});
так же как
// then 方法中有 返回值的情况
promise.then(
() => {
const value: any;
return value;
},
() => {
const reason: any;
return reason;
}
);
для окончательной стоимостиvalue
Есть много случаев, когда вы имеете дело с Thenable, обратитесь к спецификации для достижения.promise
Кромеresolve
такжеreject
, но эта часть контента относительно проста, мы объясним это позже. Первый взгляд наresolve
неразлучныthen
реализация метода. Это тожеpromise
метод ядра.
Затем реализация метода
С предыдущей реализацией мы уже можем изменить внутренности из конструктора Promise.[[PromiseStatus]]
статус и внутреннее[[PromiseValue]]
value, и у нас есть соответствующая совместимая обработка для различных значений value. Далее пришло время передать эти значения в первый параметр в методе thenonFulfilled
обработанный.
Давайте посмотрим на эту ситуацию, прежде чем объяснять:
promise2 = promise1.then(onFulfilled, onRejected);
использоватьpromise1
изthen
После метода объект вернет обещаниеpromise2
Реализация выглядит следующим образом:
class Promise {
then(onFulfilled?, onRejected?) {
// 对应上述的 promise1
const parent: any = this;
// 对应上述的 promise2
const child = new parent.constructor(() => {});
// 根据 promise 的状态选择处理方式
const state = PROMISE_STATUS[this['[[PromiseStatus]]']];
if (state) {
// promise 各状态对应枚举值 'pending' 对应 0 ,'fulfilled' 对应 1,'rejected' 对应 2
const callback = arguments[state - 1];
this.asap(() =>
this.invokeCallback(
this['[[PromiseStatus]]'],
child,
callback,
this['[[PromiseValue]]']
)
);
} else {
// 调用 then 方法的 promise 处于 pending 状态的处理逻辑,一般为异步情况。
this.subscribe(parent, child, onFulfilled, onRejected);
}
// 返回一个 promise 对象
return child;
}
}
Здесь больше бросается в глазаasap
О нем будет сказано отдельно позже. Давайте сначала разберемся с логикой.then
Метод принимает два параметра, которые задаются текущимpromise
Государство решает позвонитьonFulfilled
ещеonRejected
.
Теперь всех очень беспокоит, как выполняется код в методе THEN, например следующийconsole.log
:
promise.then(value => {
console.log(value);
});
Далее просмотрите соответствующиеinvokeCallback
метод
Обработка обратного вызова в методе then
в тогдашнем методеonFulfilled
а такжеonRejected
Все они являются необязательными параметрами.Прежде чем вы начнете дальнейшее объяснение, рекомендуется сначала понять характеристики двух параметров, упомянутых в спецификации.
Теперь, чтобы объяснитьinvokeCallback
Принимаемые параметры и их значения:
-
settled
(стабильное состояние), обещание находится вне ожидаетсяСостояние называется урегулированным, а значение урегулированного может бытьfulfilled
илиrejected
-
child
объект обещания, который будет возвращен -
callback
согласно сsettled
ВыбраноonFulfilled
илиonRejected
Перезвоните -
detail
Значение (окончательное значение) или причина (отказ) промиса текущего вызова метода then
Обратите внимание здесьsettled
а такжеdetail
,settled
используется для обозначенияfulfilled
илиrejected
, деталь используется для обозначенияvalue
илиreason
все это имеет смысл
Зная это, нужно лишь обратиться к тому, как спецификация рекомендует реализовать соответствующую обработку:
private invokeCallback(settled, child, callback, detail) {
// 1、是否有 callback 的对应逻辑处理
// 2、回调函数执行后是否会抛出异常,即相应处理
// 3、返回值不能为自己的逻辑处理
// 4、promise 结束(执行结束或被拒绝)前不能执行回调的逻辑处理
// ...
}
Логика для обработки дана, а остальные методы реализации читатели могут реализовать самостоятельно или посмотреть реализацию исходного кода этого проекта. Рекомендуется, чтобы все реализации ссылались на спецификацию для реализации, и во время реализации могут возникать пропуски или неправильная обработка. (ps: пришло время проверить надежность зависимости)
На данный момент все дело в синхронном случае. Обещание известно как бог асинхронной обработки, как может быть меньше соответствующей реализации. Для борьбы с асинхронностью существуют режимы публикации обратного вызова и подписки.
затем асинхронная обработка
Здесь нужно обработать случай, когда промис, вызвавший метод then, находится в состоянии ожидания.
Когда это произойдет? Взгляните на этот код:
const promise = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
});
promise.then(value => {
console.log(value);
});
Код написан здесь, если это произойдет. Наше обещание на самом деле не работает. из-заsetTimeout
Это ненормальная операция, когда внутренний метод then выполняется синхронно, разрешение вообще не выполняется, то есть вызывается обещание метода then.[[PromiseStatus]]
в настоящее время «ожидает рассмотрения»,[[PromiseValue]]
В настоящее время не определено, добавьте пару сейчасpending
Обратный вызов в состоянии не имеет смысла, и в спецификации упоминается, что обратный вызов метода then должен быть установлен(Я говорил это раньше) вызовет соответствующий обратный вызов.
Или нам не нужно рассматривать, вызвано ли это асинхронностью, просто нужно уточнить одну вещь. Есть такая ситуация, может быть вызвано состояние обещания метода thenpending
.
В настоящее время должен быть механизм для решения этой ситуации, и соответствующая реализация кода:
private subscribe(parent, child, onFulfillment, onRejection) {
let {
subscribes,
subscribes: { length }
} = parent;
subscribes[length] = child;
subscribes[length + PROMISE_STATUS.fulfilled] = onFulfillment;
subscribes[length + PROMISE_STATUS.rejected] = onRejection;
if (length === 0 && PROMISE_STATUS[parent['[[PromiseStatus]]']]) {
this.asap(this.publish);
}
}
subscribe
принимает 4 аргументаparent
,child
,onFulfillment
,onRejection
-
parent
объект обещания, для которого в данный момент вызывается метод then -
child
объект обещания, который будет возвращен методом then -
onFulfillment
Первый параметр метода THEN -
onFulfillment
Второй параметр, затем метод
хранить в массивеsubscribe
, который в основном сохраняет возвращаемый объект обещания и соответствующийonFulfillment
а такжеonRejection
Перезвоните.
удовлетворитьsubscribe
это новый случай и объект обещания, который вызывает метод then[[PromiseStatus]]
значение не «ожидающее», затем вызовитеpublish
метод. То есть в случае асинхронности это не будет называтьсяpublish
метод.
Кажется, что этоpublish
Метод, связанный с выполнением обратного вызова.
В асинхронном случае, когда будет запущен обратный вызов?Вы можете просмотреть то, что я объяснил ранееfulfill
метод:
private fulfill(value: any) {
this['[[PromiseStatus]]'] = 'fulfilled';
this['[[PromiseValue]]'] = value;
// 用于处理异步情况
if (this.subscribes.length !== 0) {
this.asap(this.publish);
}
}
когда удовлетворенthis.subscribes.length !== 0
Когда триггерpublish
.也就是说当异步函数执行完成后调用resolve
метод, когда есть такой вызовsubscribes
Решение функции обратного вызова внутри.
Это гарантируетthen
Функция обратного вызова в методе будет запущена только после завершения выполнения асинхронной функции. Тогда посмотрите соответствующиеpublish
метод
метод публикации
Прежде всего ясно, чтоpublish
публикуется, проходитinvokeCallback
для вызова функции обратного вызова. В этом проекте только сsubscribes
Связанный. Просто посмотрите на код ниже:
private publish() {
const subscribes = this.subscribes;
const state = this['[[PromiseStatus]]'];
const settled = PROMISE_STATUS[state];
const result = this['[[PromiseValue]]'];
if (subscribes.length === 0) {
return;
}
for (let i = 0; i < subscribes.length; i += 3) {
// 即将返回的 promise 对象
const item = subscribes[i];
const callback = subscribes[i + settled];
if (item) {
this.invokeCallback(state, item, callback, result);
} else {
callback(result);
}
}
this.subscribes.length = 0;
}
затем краткое изложение метода
На данный момент мы реализовали метод then в обещании, что означает, что реализованное в настоящее время обещание имеет возможность обрабатывать асинхронные потоки данных. Реализация метода then неотделима от указаний спецификации.Пока вы ссылаетесь на описание метода then в спецификации, остальное — просто логическая обработка.
На данный момент основная функция обещания завершена, т. е. внутренняя[[Resolve]]
а такжеthen
метод. Давайте кратко рассмотрим остальную часть API.
Реализация API синтаксического сахара
catch и, наконец, являются синтаксическим сахаром
-
catch
принадлежатьthis.then(null, onRejection)
-
finally
принадлежатьthis.then(callback, callback);
Обещание также обеспечиваетresolve
,reject
,all
,race
Статический метод , чтобы облегчить вызов цепочки, вышеупомянутые методы будут возвращать новый объект обещания для вызова цепочки.
В основном доresolve
, а теперь посмотри
reject
reject
обработка сresolve
Небольшое отличие состоит в том, что ему не нужно иметь дело с возможным случаем.В правиле упоминается, что значение reject является причиной.Рекомендуется реализовать код экземпляра ошибки следующим образом:
private mockReject(reason: any) {
this['[[PromiseStatus]]'] = 'rejected';
this['[[PromiseValue]]'] = reason;
this.asap(this.publish);
}
static reject(reason: any) {
let Constructor = this;
let promise = new Constructor(() => {});
promise.mockReject(reason);
return promise;
}
private mockReject(reason: any) {
this['[[PromiseStatus]]'] = 'rejected';
this['[[PromiseValue]]'] = reason;
this.asap(this.publish);
}
all & race
На базе предыдущего API не сложно все расширить и погонять. Давайте сначала посмотрим на роль двух:
-
all используется для обработки набора промисов, когда все промисы разрешаются или промис отклоняется, он возвращает массив значений или причину отклонения
-
Смысл гонки в том, чтобы посмотреть, кто самый быстрый в наборе промисов, затем использовать результат этого промиса
Соответствующий код реализации:
// all
let result = [];
let num = 0;
return new this((resolve, reject) => {
entries.forEach(item => {
this.resolve(item).then(data => {
result.push(data);
num++;
if (num === entries.length) {
resolve(result);
}
}, reject);
});
});
// race
return new this((resolve, reject) => {
let length = entries.length;
for (let i = 0; i < length; i++) {
this.resolve(entries[i]).then(resolve, reject);
}
});
комбинация синхронизации
Если есть асинхронные функции, которые нужно выполнять последовательно, можно использовать следующие методы:
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
Временная композиция в ES7 может быть выполнена с помощьюasync/await
выполнить
for (let f of [func1, func2]) {
await f();
}
Дополнительные сведения об использовании см.это
дополнение к знаниям
Появились промисы для лучшей обработки асинхронных потоков данных или, как их часто называют, callback hell. Обратный вызов, упомянутый здесь, относится к асинхронному типу. Если он не асинхронный, функцию обратного вызова обычно не нужно использовать. Давайте рассмотрим несколько концепций, возникающих в процессе реализации промисов:
-
Перезвоните
-
Асинхронный и синхронный
-
EventLoop
-
asap
Перезвоните
Английское определение функции обратного вызова:
Обратный вызов — это функция, которая передается в качестве аргумента другой функции и выполняется после завершения родительской функции.
Буквально функция обратного вызова является параметром, и эта функция передается в качестве параметра другой функции.Когда функция выполняется, выполняется переданная функция. Этот процесс называется обратным вызовом.
В JavaScript функция обратного вызова определяется следующим образом: функция A передается в качестве параметра (ссылки на функцию) другой функции B, и эта функция B выполняет функцию A. Предположим, что функция A называется функцией обратного вызова. Если имя (функция-выражение) отсутствует, она называется анонимной функцией обратного вызова.
Прежде всего, необходимо объявить, что callback-функция — это только реализация, а не конкретная реализация асинхронного режима. Функция обратного вызова также может использоваться в синхронных (блокирующих) сценариях и некоторых других сценариях.
Функции обратного вызова необходимо отличать от асинхронных функций.
Асинхронные функции и синхронные функции
-
Если вызывающая сторона может получить ожидаемый результат при возврате функции, это синхронная функция.
-
Если вызывающая сторона не может получить ожидаемый результат при возврате функции, но должна получить его определенными средствами в будущем, то функция является асинхронной.
Так что же такое асинхронный и синхронный?
Асинхронный и синхронный
Прежде всего, должно быть понятно, что среда выполнения языка Javascript является «однопоточной». Так называемый «одиночный поток» означает, что одновременно может выполняться только одна задача. Если задач несколько, они должны быть поставлены в очередь, предыдущая задача завершена, следующая задача выполнена и так далее.
Этот режим вызовет проблему блокировки.Для решения этой проблемы язык Javascript делит режим выполнения задач на два типа: Синхронный и Асинхронный.
Но следует отметить, что:Асинхронный механизм завершается двумя или более резидентными потоками браузера вместе., однопоточность и асинхронность Javascript должны быть болееПринадлежность к поведению браузера. То есть сам Javascript является однопоточным,нет асинхронных функций.
Поскольку сценарием использования JavaScript является браузер, сам браузер является типичным рабочим потоком GUI.Рабочий поток GUI реализован как процесс обработки событий в большинстве систем, что позволяет избежать блокирующих взаимодействий, таким образом генерируя асинхронный ген JavaScript. Все методы и функции, использующие асинхронность, выполняются другим потоком браузера.
Темы в браузере
-
Поток триггера события браузераКогда событие запускается, поток добавляет событие в конец очереди ожидания, ожидая, пока механизм JavaScript обработает его. Эти события могут быть выполняемым в данный момент блоком кода, например:задача на времяили из других потоков ядра браузера, таких какнажмите,Асинхронный запрос AJAXд., но так как JavaScript однопоточный, все эти события должны ставиться в очередь для обработки движком JavaScript;
-
синхронизированный триггерный потоксчетчик таймера браузеранетПодсчитывается движком JavaScript, поскольку движок JavaScript является однопоточным, если он находится в состоянии заблокированного потока, это повлияет на точность синхронизации, поэтому более разумно использовать отдельный поток для синхронизации и запуска синхронизации;
-
Поток асинхронных HTTP-запросовПосле подключения XMLHttpRequest через браузер открывается новый запрос потока.Если установлена функция обратного вызова, асинхронный поток сгенерирует событие изменения состояния и поместит его в очередь обработки движка JavaScript.ожидание обработки;
Благодаря вышеизложенному пониманию вы можете узнать, что JavaScript на самом делеАсинхронность достигается за счет взаимодействия и сотрудничества между потоком движка JS и другими потоками в браузере..
Но функция обратного вызова при добавлении к выполнению конкретного потока JS-движка? Какой порядок исполнения?
Тогда посмотрите на соответствующие механизмы с EventLoop
EventLoop
Давайте сначала рассмотрим некоторые понятия, стек, куча, очередь, прямо выше:
-
Те операции, которые не требуют функции обратного вызова, могут быть классифицированы как стек.
-
Куча используется для хранения объявленных переменных и объектов.
-
Как только асинхронная задача получит ответ, она будет помещена в очередь Queue.
Грубый процесс выглядит следующим образом:
Поток движка JS используется для выполненияСинхронизировать задачу, когда все задачи синхронизацииПосле выполнения стек очищается, затем прочитайте отложенную задачу в очереди сообщений и поместитеСоответствующая функция обратного вызова помещается в стек, один поток начинает выполнение новой задачи синхронизации.
Поток движка JS считывает задачи из очереди сообщений в непрерывном цикле.После очистки каждого стека, будет прочитать новые задачи в очереди сообщений, если нет новой задачи, она будет ждать, пока не будет новая задача, которая называетсяцикл событий.
Я не знаю, кто нарисовал эту картинку, она действительно великолепна! Давайте позаимствуем ее, чтобы описать общий процесс AJAX:
Запросы AJAX — это трудоемкие асинхронные операции, и в браузерах есть специальные потоки для их обработки. Асинхронные задачи запускаются при наличии кода, вызывающего AJAX в основном потоке. Работа по выполнению этой асинхронной задачи передается потоку AJAX,Основной поток не ожидает результата этой асинхронной операции.Но потом выполнить. Предположим, что код основного потока выполняется в определенное время, то есть стек в это время пуст. В более ранние моменты, после выполнения асинхронной задачи, сообщение сохранялось в Очереди, так что, когда Стек пуст, из него берется функция обратного вызова для выполнения.
То, что работает за этим, называетсяEventLoop
, с приведенным выше общим пониманием. Давайте заново поймем EventLoop:
«Сторонником» асинхронности являются циклы событий. Асинхронность здесь следует точно называть циклами событий браузера или циклами событий среды выполнения javaScript, потому что в ECMAScript нет циклов событий, а циклы событий определены в стандарте HTML.
Цикл событий переводится как цикл событий, который можно понимать как способ достижения асинхронности Давайте взглянем на раздел определения цикла событий в стандарте HTML:
Чтобы координировать события, взаимодействие с пользователем, сценарии, рендеринг, работу в сети и т. д., пользовательские агенты должны использовать цикл обработки событий, описанный в этом разделе.
События, взаимодействие с пользователем, сценарии, рендеринг, сетевое взаимодействие — все это знакомые вещи, и все они координируются циклом событий. Запустите событие щелчка, сделайте запрос ajax, и за этим работает цикл обработки событий.
task
Цикл событий имеет одну или несколько очередей задач. Каждая задача исходит из определенного источника задачи.Например, для событий мыши и клавиатуры может быть предусмотрена очередь задач, а другие события представляют собой отдельную очередь. Для событий мыши и клавиатуры можно выделить больше времени, чтобы обеспечить плавное взаимодействие.
Задача также известна какmacrotask, очередь задач относительно проста для понимания, это очередь в порядке очереди, которая предоставляет задачи из указанного источника задач.
источник задачи задачи:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
microtask
Каждый цикл событий имеет очередь микрозадач, и микрозадача будет поставлена в очередь в очередь микрозадач, а не в очередь задач. Очередь микрозадач чем-то похожа на очередь задач.Оба являются очередями в порядке очереди, а задачи предоставляются указанным источником задач.Разница в том, что в цикле событий существует только одна очередь микрозадач.
Источниками микрозадач обычно считаются:
- process.nextTick
- promises
- Object.observe
- MutationObserver
Вопрос из интервью о EventLoop
console.log('start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(function() {
console.log('promise1');
})
.then(function() {
console.log('promise2');
});
console.log('end');
// start
// end
// promise1
// promise2
// setTimeout
Приведенная выше последовательность выполняется в хроме, интересно протестировано в сафари 9.1.2, обещание1 обещание2 будет после setTimeout, а в сафари 10.0.1 получил тот же результат, что и в хроме. Разница между промисами в разных браузерах заключается в том, что некоторые браузеры помещают их в очередь макрозадач, а некоторые — в очередь микрозадач.
Сводка цикла событий
В цикл событий вовлечено много вещей, но в этой статье я боюсь отклониться от фокуса, здесь я упомяну только те знания, которые могут быть связаны с промисами. Если вы хотите узнать больше об учениках, рекомендуем прочитатьэтоопределенно принесет много пользы.
что как можно скорее
as soon as possible
Аббревиатура в английском языке, роль обещания — как можно быстрее реагировать на изменения.
существуетОбещания/спецификация A+изNotes 3.1Упоминается, что тогдашний метод обещания может быть реализован с использованием механизма «макрозадачи» или механизма «микрозадачи».
В этом проекте с помощьюmacro-task
механизм
private asap(callback) {
setTimeout(() => {
callback.call(this);
}, 1);
}
Или шаблон MutationObserver:
function flush() {
...
}
function useMutationObserver() {
var iterations = 0;
var observer = new MutationObserver(flush);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
return function () {
node.data = iterations = ++iterations % 2;
};
}
первый раз смотрю этоuseMutationObserver
函数总会很有疑惑,MutationObserver
Разве он не используется для наблюдения за изменениями в dom, так какой смысл создавать узел из ничего, чтобы многократно изменять его содержимое, чтобы вызвать наблюдаемую функцию обратного вызова?
Ответ заключается в использованииMutation
События могут выполнять операции асинхронно (функция сброса в примере), одно — реагировать на изменения как можно быстрее, а другое — исключать повторные вычисления.
Или в среде узла:
function useNextTick() {
return () => process.nextTick(flush);
}
внешняя безопасность
Сейчас интерфейс находится в расцвете модульной разработки, столкнувшись сnpmИз этих тысяч «фирменных сумок» многие такие маленькие белые, как я, неизбежно потеряют себя и постепенно потеряют чувство безопасности.
Вы хотите использовать его, или вы время от времени изучаете основные принципы, как автор, смотрите на код других людей и сами шлепаете его, чтобы понять его истинное значение, чтобы улучшить свое чувство безопасности.
Еще было время заняться фронтом, а те, кто утонул в деле, реально вздохнули!Вспомним, как-то раз я чуть было не оформлял входной фронтенд наконец-то, на уме поневоле захотелось пойти покушать, тогда пишите это сейчас!
Эпилог
Я много писал, и это утомительно. Я надеюсь быть полезным.