предисловие
Некоторое время назад я записал некоторые распространенные варианты использования промисов, а эта статья идет на один уровень глубже, чтобы проанализировать, как реализован механизм правил промисов. ps: Эта статья подходит для людей, которые уже знакомы с использованием промисов.Если вы мало знаете об их использовании, вы можете перейти к моей предыдущей статье.Сообщение блога.
Исходный код обещания этой статьи основан наСпецификация Обещание/A+писать (не хочу видеть английскую версию ходаКитайский перевод спецификации Promise/A+)
Введение
Чтобы всем было легче понять, мы начнем со сцены, чтобы каждый думал шаг за шагом, и я думаю, вам будет легче понять.
Рассмотрим следующую обработку запроса, чтобы получить идентификатор пользователя.
//例1
function getUserId() {
return new Promise(function(resolve) {
//异步请求
http.get(url, function(results) {
resolve(results.id)
})
})
}
getUserId().then(function(id) {
//一些处理
})
getUserId
метод возвращаетpromise
, через которыйthen
регистрация метода (примечание注册
слово) вpromise
Обратный вызов для выполнения при успешном выполнении асинхронной операции. Этот метод выполнения делает асинхронные вызовы очень простыми.
Принципиальный анализ
Тогда что-то вроде этогоPromise
Как этого добиться?其实按照上面一句话,实现一个最基础的雏形还是很easy的。
Предельно простое обещание
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
Приведенный выше код очень прост, и общая логика выглядит следующим образом:
- передача
then
метод, захочетсяPromise
Обратный вызов, который выполняется при успешном выполнении асинхронной операции, помещается вcallbacks
Очередь, по сути, заключается в регистрации callback-функции, которую можно мыслить в сторону режима наблюдателя; - Создайте
Promise
Функция, переданная в экземпляре, получит параметр типа функции, то естьresolve
, который получает значение параметра, представляющее результат, возвращаемый асинхронной операцией. Когда один шаг операции выполнен успешно, пользователь вызоветresolve
метод, фактическая операция в настоящее время заключается вcallbacks
Обратные вызовы в очереди выполняются один за другим;
можно комбинировать例1
Из кода в , сначалаnew Promise
когда перейти кpromise
Функция, которая отправляет асинхронный запрос, а затем вызываетpromise
объектthen
Атрибут, зарегистрируйте функцию обратного вызова для успешного запроса, а затем вызовите, когда асинхронный запрос будет успешно отправлен.resolve(results.id)
метод, который выполняетthen
Массив обратных вызовов, зарегистрированных методом.
Я считаю, что внимательный человек должен уметь это видеть,then
Методы должны иметь возможность связываться в цепочку, но самая основная и простая версия выше, очевидно, не поддерживает цепочку. хочу сделатьthen
Метод поддерживает связанные вызовы, что на самом деле очень просто:
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this;
};
См. Цепной вызов, подобный следующему, может быть достигнут с помощью простого предложения:
// 例2
getUserId().then(function (id) {
// 一些处理
}).then(function (id) {
// 一些处理
});
Добавьте механизм задержки
Внимательные студенты должны обнаружить, что с приведенным выше кодом может быть проблема: еслиthen
Прежде чем метод зарегистрирует обратный вызов,resolve
Функция выполнена, что делать? Напримерpromise
Внутренняя функция является синхронной функцией:
// 例3
function getUserId() {
return new Promise(function (resolve) {
resolve(9876);
});
}
getUserId().then(function (id) {
// 一些处理
});
Это явно запрещено, т.Promises/A+
Спецификация явно требует, чтобы обратные вызовы выполнялись асинхронно, чтобы обеспечить согласованный и надежный порядок выполнения. Итак, мы собираемся добавить некоторую обработку, чтобы убедиться, чтоresolve
Перед казнью,then
Метод зарегистрировал все обратные вызовы. Мы можем изменить это такresolve
функция:
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)
}
Идея приведенного выше кода также очень проста, то есть черезsetTimeout
механизм, воляresolve
Логика, которая выполняет обратный вызов вJS
конец очереди задач, чтобы убедиться, чтоresolve
При выполнении,then
Функция обратного вызова метода зарегистрирована.
Однако, похоже, проблема все же есть, над ней можно подумать: еслиPromise
Асинхронная операция выполнена успешно. В это время будут выполнены обратные вызовы, зарегистрированные до успешной асинхронной операции.Promise
Вызывается после успешного выполнения асинхронной операции.then
Зарегистрированный обратный вызов больше никогда не будет выполняться, а это явно не то, чего мы хотим.
присоединиться к состоянию
Итак, чтобы решить проблему, поднятую в предыдущем разделе, мы должны добавить механизм состояния, известный какpending
,fulfilled
,rejected
.
Promises/A+
2.1 в спецификацииPromise States
четко оговаривается,pending
может быть преобразован вfulfilled
илиrejected
и может быть преобразован только один раз, то есть, еслиpending
Перевести вfulfilled
состояние, то оно уже не может быть преобразовано вrejected
. а такжеfulfilled
а такжеrejected
состояние можно определить толькоpending
Два не могут быть преобразованы друг в друга. Одна картинка стоит тысячи слов:
Улучшенный код выглядит следующим образом:
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}
Идея приведенного выше кода заключается в следующем:resolve
При выполнении установит состояние наfulfilled
, после этого звонкаthen
Добавленные новые обратные вызовы выполняются немедленно.
Здесь нет местаstate
установить какrejected
, чтобы все могли сосредоточиться на основном коде, после этого вопроса будет специальный раздел.
Связанные обещания
Итак, снова возникает проблема: если пользователь регистрируется в функции then, она по-прежнемуPromise
, как решить? Например, следующее例4
:
// 例4
getUserId()
.then(getUserJobById)
.then(function (job) {
// 对job的处理
});
function getUserJobById(id) {
return new Promise(function (resolve) {
http.get(baseUrl + id, function(job) {
resolve(job);
});
});
}
Я считаю, что этот сценарий был использованpromise
Все знают, что их будет много, поэтому примерно так и получается так называемая цепочкаPromise
.
цепьPromise
значит на данный моментpromise
достигатьfulfilled
статус, начать следующийpromise
(рядом сpromise
). Итак, как нам связаться с текущимpromise
и соседиpromise
Шерстяная ткань? (Это сложный момент здесь).
На самом деле, это не так уж и сложно, еслиthen
внутри методаreturn
Одинpromise
Просто хорошо.Promises/A+
Вот что написано в спецификации 2.2.7 (смайлик)~
Вот взгляд на эту скрытую тайнуthen
Методы иresolve
Код модификации метода:
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
//如果then中没有传递任何东西
if(!callback.onFulfilled) {
callback.resolve(value);
return;
}
var ret = callback.onFulfilled(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
fn(resolve);
}
мы объединяем例4
код, проанализируйте логику приведенного выше кода, для удобства чтения ставлю例4
Код размещен здесь:
// 例4
getUserId()
.then(getUserJobById)
.then(function (job) {
// 对job的处理
});
function getUserJobById(id) {
return new Promise(function (resolve) {
http.get(baseUrl + id, function(job) {
resolve(job);
});
});
}
-
then
В методе создайте и верните новыйPromise
например, это сериалPromise
и поддерживает связанные вызовы. -
handle
путьpromise
внутренний метод.then
Параметры, переданные в методonFulfilled
и создавать новыеPromise
прошел в экземпляреresolve
оба былиpush
к текущемуpromise
изcallbacks
очередь, которая является текущим соединениемpromise
и соседиpromise
Ключ (здесь мы должны проанализировать роль ручки). -
getUserId
Сгенерированоpromise
(упоминается какgetUserId promise
) асинхронная операция завершается успешно и выполняется ее внутренний методresolve
, входящий параметр является результатом асинхронной операцииid
- передача
handle
метод обработкиcallbacks
Обратные вызовы в очереди:getUserJobById
Метод, генерировать новыйpromise
(getUserJobById promise
) - перед исполнением
getUserId promise
изthen
метод сгенерировал новыйpromise
(называетсяbridge promise
)изresolve
метод, входные параметрыgetUserJobById promise
. В этом случаеresolve
метод передан вgetUserJobById promise
изthen
метод и возврат напрямую. - существует
getUserJobById promise
Когда асинхронная операция завершится успешно, выполните ееcallbacks
обратный вызов через:getUserId bridge promise
серединаresolve
метод - окончательное исполнение
getUserId bridge promise
по соседствуpromise
изcallbacks
обратный звонок в .
Чтобы быть более простым, вы можете посмотреть на следующую картинку, картинка стоит тысячи слов (все нарисовано в соответствии с вашим собственным пониманием, пожалуйста, поправьте меня, если я ошибаюсь):
обработка отказов
При сбое асинхронной операции отметьте ее статус какrejected
и выполните зарегистрированный обратный вызов ошибки:
//例5
function getUserId() {
return new Promise(function(resolve) {
//异步请求
http.get(url, function(error, results) {
if (error) {
reject(error);
}
resolve(results.id)
})
})
}
getUserId().then(function(id) {
//一些处理
}, function(error) {
console.log(error)
})
обработано доfulfilled
Имея опыт работы с состоянием, очень легко поддерживать обработку ошибок, достаточно добавить новую логику регистрации обратных вызовов и обработки изменений состояния:
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? callback.resolve : callback.reject;
cb(value);
return;
}
ret = cb(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve, reject);
return;
}
}
state = 'fulfilled';
value = newValue;
execute();
}
function reject(reason) {
state = 'rejected';
value = reason;
execute();
}
function execute() {
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
fn(resolve, reject);
}
Приведенный выше код добавляет новыйreject
метод, который называется, когда асинхронная операция не удается, и извлекаетresolve
а такжеreject
общие части, образующиеexecute
метод.
Всплытие ошибок — это функция, которую приведенный выше код уже поддерживает и которая очень полезна. существуетhandle
Когда обнаруживается, что обратный вызов для отказа асинхронной операции не указан, он будет напрямуюbridge promise
(then
возвращается функциейpromise
, то же самое ниже) установлен наrejected
Статус, чтобы добиться эффекта выполнения последующих обратных вызовов с ошибкой. Это помогает упростить сериализациюPromise
Поскольку набор асинхронных операций часто соответствует реальной функции, метод обработки сбоев обычно непротиворечив:
//例6
getUserId()
.then(getUserJobById)
.then(function (job) {
// 处理job
}, function (error) {
// getUserId或者getUerJobById时出现的错误
console.log(error);
});
Обработка исключений
Внимательные студенты подумают: а что, если в коде есть ошибка при выполнении успешного обратного вызова и неудачного обратного вызова? Для таких исключений вы можете использоватьtry-catch
поймать ошибку иbridge promise
установить какrejected
условие.handle
Метод модифицируется следующим образом:
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? callback.resolve : callback.reject;
cb(value);
return;
}
try {
ret = cb(value);
callback.resolve(ret);
} catch (e) {
callback.reject(e);
}
}
Если в асинхронной операции, выполнить несколько разresolve
илиreject
Последующие обратные вызовы будут обрабатываться повторно, что можно решить с помощью встроенного флага.
Суммировать
Когда я впервые читал исходный код промиса, я не очень хорошо понимал механизм работы функций then и resolve, но если успокоиться и вывести его по логике выполнения промиса, то понять несложно. Здесь следует обратить внимание на то, что функция then в промисе регистрирует только тот код, который необходимо выполнить позже, реальное выполнение выполняется в методе разрешения, после уточнения этого слоя это сэкономит много усилий. для анализа исходного кода.
Теперь рассмотрим процесс реализации Promise, который в основном использует шаблон наблюдателя в шаблоне проектирования:
- Метод наблюдателя регистрируется в объекте Promise наблюдателя через Promise.Prototype.Then и Promise.Prototype.catch, и для вызова возвращается новый объект Promise.
- Наблюдатель управляет внутренними ожидающими, выполненными и отклоненными переходами состояний, и в то же время он активно запускает переходы состояний и уведомляет наблюдателя с помощью методов разрешения и отклонения, переданных в конструкторе.
использованная литература
Глубокое понимание промисов
JavaScript Promises ... In Wicked Detail