Примечания к выпуску
- Время обновления: 23 января 2019 г.
Я сделал выполнение тогдашнего метода синхронным, что не соответствует спецификации.
В ["Promises/A+ Specification"][6] в подразделе [Then Method] [Call Timing] говорится: "onFulfilled и onRejected могут быть вызваны только тогда, когда стек среды выполнения содержит только код платформы", вот особый вид в Комментарий.
Поэтому я будуonFulfilled
а такжеonRejected
Код находится в "then
Выполняется в новом стеке выполнения после цикла событий, в котором был вызван метод, черезsetTimeout
Метод помещает задачу в конец текущего раунда очереди задач. Код добавлен в последнюю часть - шаг девятый.
Что касается механизма работы очереди задач, если вам интересно, вы можете прочитать работу г-на Жуана Ифэна.«Подробное объяснение механизма выполнения JavaScript: снова поговорим о цикле событий»
Функция реализации:
- осуществленный
Promise
Базовые функции, как и нативные, подходят для асинхронных и синхронных операций, в том числе:MyPromise.prototype.then()
-
MyPromise.prototype.catch()
с роднымPromise
немного отличается MyPromise.prototype.finally()
MyPromise.all()
MyPromise.race()
MyPromise.resolve()
MyPromise.reject()
-
rejected
Также был решен процесс всплытия состояния: если отклонение текущего промиса не захвачено, оно будет всплывать до конца, пока не будет поймано -
MyPromise
После того, как состояние изменилось, его нельзя изменить снова
Недостатки:
- Когда ошибка кода перехватывается уловом, информация о подсказке (захваченный объект ошибки) больше, чем нативный промис.
- Код написан на es6, и будет считаться написанным на es5, чтобы его можно было применять к проектам es5; если он написан на es5, стрелочные функции не используются, и эту проблему необходимо учитывать
тестовое задание:index.html
- На этой странице 27 тестовых примеров, которые проверяют каждую функцию, каждый метод и некоторые специальные тесты, могут быть какие-то упущения, можете поиграть, если интересно;
- Визуальная работа удобна для тестирования, каждый раз при запуске примера вы можете видеть результаты при открытии отладочной консоли, рекомендуется открывать ее одновременно.
index.js
Играйте, наблюдая за кодом; - Тот же набор кода, выше
MyPromise
Результат операции, ниже оригиналPromise
результат операции;
награда
- Этот процесс очень радует, я могу сам бросить вызов оригинальным вещам, это мой первый раз;
- Провел много дней, бросая
Promise
Сначала поймите его, затем подумайте о нем и, наконец, шаг за шагом осознайте его функцию, и его понимание будет продолжать углубляться и становиться все более и более полным; - При написании новой функции обнаруживается, что в этой новой функции отсутствует предыдущая функция, в это время необходимо понять взаимосвязь между ними и переосмыслить предыдущую функцию, в этом повторении несомненно углубился слой понимания;
-
then/catch
Метод самый трудный, постоянно возиться; - Наконец, после того, как все функции были реализованы, я вспомнил ключевой момент «После того, как состояние промиса определено, его нельзя изменить», и была добавлена некоторая логика для его решения. Поэтому этот процесс трудно защитить от утечек, и, возможно, в текущем коде все еще есть какие-то скрытые проблемы, которые не были обнаружены.
-
reject
Всплывание состояния — сложная проблема, но я специально не упомянул об этом в следующем коде, и у меня нет возможности прояснить это.
код
Вставьте код ниже, включая весь мыслительный процесс, это будет немного долго
Чтобы написать логику спецификации, я использую следующие комментарии для обозначения, и все изменение кода только отмечает начало этой кучи.
//++
- добавлен код
//-+
- модифицированный код
Первым шагом является определение класса MyPromise.
Вы можете выбрать любое имя, мое — MyPromise, которое не заменяет оригинальный Promise.
- Конструктор передает функцию обратного вызова
callback
. когда новыйMyPromise
объект, нам нужно запустить этот обратный вызов, иcallback
Он также имеет два параметра, которыеresolve
а такжеreject
, они также представлены в виде функций обратного вызова; - Несколько переменных определены для сохранения некоторых текущих результатов и статусов, очередей событий, см. примечания;
- выполнить функцию
callback
когда, еслиresolve
состояние, сохраните результат вthis.__succ_res
, статус помечается как успешный, еслиreject
статус, операция аналогична; - Также определяет наиболее часто используемые
then
метод, являющийся методом-прототипом; - воплощать в жизнь
then
метод, определить, является ли состояние объекта успешным или неудачным, выполнить соответствующий обратный вызов соответственно и передать результат в обработку обратного вызова; - получить здесь
...arg
и входящие параметры...this.__succ_res
Оба используют оператор распространения, чтобы иметь дело с несколькими параметрами, он передается вthen
обратный вызов метода.
callback
Обратный вызов использует здесь функции стрелок,this
указывает на текущийMyPromise
объект, поэтому нет необходимости иметь дело сthis
вопрос.
class MyPromise {
constructor(callback) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
//箭头函数绑定了this,如果使用es5写法,需要定义一个替代的this
callback((...arg) => {
this.__succ_res = arg;
this.status = 'success';
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
});
}
then(onFulfilled, onRejected) {
if (this.status === 'success') {
onFulfilled(...this.__succ_res);
} else if (this.status === 'error') {
onRejected(...this.__err_res);
};
}
};
сюда,MyPromise
Вы можете просто реализовать некоторый синхронный код, например:
new MyPromise((resolve, reject) => {
resolve(1);
}).then(res => {
console.log(res);
});
//结果 1
Второй шаг — добавить асинхронную обработку
При выполнении асинхронного кодаthen
Метод будет выполнен до асинхронного результата, и результат не может быть получен из вышеуказанной обработки.
- Во-первых, поскольку он асинхронный,
then
метод вpending
выполняется, поэтому добавьтеelse
; - воплощать в жизнь
else
Когда у нас еще нет результата, мы можем только поместить обратный вызов, который необходимо выполнить, в очередь и выполнить его, когда это необходимо, поэтому определяется новая переменная.this.__queue
сохранить очередь событий; - Когда выполняется асинхронный код, на этот раз
this.__queue
Все обратные вызовы в очереди выполняются один раз, если этоresolve
статус, выполните соответствующийresolve
код.
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列 //++
//箭头函数绑定了this,如果使用es5写法,需要定义一个替代的this
fn((...arg) => {
this.__succ_res = arg;
this.status = 'success';
this.__queue.forEach(json => { //++
json.resolve(...arg);
});
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
this.__queue.forEach(json => { //++
json.reject(...arg);
});
});
}
then(onFulfilled, onRejected) {
if (this.status === 'success') {
onFulfilled(...this.__succ_res);
} else if (this.status === 'error') {
onRejected(...this.__err_res);
} else { //++
this.__queue.push({resolve: onFulfilled, reject: onRejected});
};
}
};
В этот момент,MyPromise
Уже можно реализовать какой-нибудь простой асинхронный код. прецедентindex.html
, эти два примера уже могут быть реализованы.
1 异步测试--resolve
2 异步测试--reject
Третий шаг, присоединяйтесь к цепочке вызовов
На самом деле, роднойPromise
Метод then объекта возвращаетPromise
объект, новыйPromise
объект, чтобы всегда поддерживать связанные вызовыthen
опускаться. . .
а также,then
метод может получить предыдущийthen
Метод обрабатывает результат возврата. согласно сPromise
Характеристический анализ этого возвращаемого результата имеет 3 возможности:
-
MyPromise
объект; - имеют
then
объект метода; - другие значения. Эти три случая рассматриваются отдельно.
- Первое, о чем идет речь, это,
then
метод возвращаетMyPromise
объект, функция обратного вызова которого получаетresFn
а такжеrejFn
Две функции обратного вызова; - Инкапсулируйте код обработки успешного состояния как
handle
Функция, которая принимает успешный результат в качестве параметра; -
handle
В функции, согласноonFulfilled
Разные возвращаемые значения, разная обработка:- Во-первых, получить
onFulfilled
возвращаемое значение (если есть), сохраненное какreturnVal
; - Затем, судите
returnVal
Существует ли тогда метод, который включает случаи 1 и 2, рассмотренные выше (этоMyPromise
объект или имеетthen
другие объекты метода), для нас все равно; - После этого, если есть
then
метод, немедленно вызовите егоthen
метод, перебрасывая результаты успеха и неудачи на новыйMyPromise
Функция обратного вызова объекта, если нет, то результат передается вresFn
Перезвоните.
- Во-первых, получить
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//箭头函数绑定了this,如果使用es5写法,需要定义一个替代的this
fn((...arg) => {
this.__succ_res = arg;
this.status = 'success';
this.__queue.forEach(json => {
json.resolve(...arg);
});
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...arg);
});
});
}
then(onFulfilled, onRejected) {
return new MyPromise((resFn, rejFn) => { //++
if (this.status === 'success') {
handle(...this.__succ_res); //-+
} else if (this.status === 'error') {
onRejected(...this.__err_res);
} else {
this.__queue.push({resolve: handle, reject: onRejected}); //-+
};
function handle(value) { //++
//then方法的onFulfilled有return时,使用return的值,没有则使用保存的值
let returnVal = onFulfilled instanceof Function && onFulfilled(value) || value;
//如果onFulfilled返回的是新MyPromise对象或具有then方法对象,则调用它的then方法
if (returnVal && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {//其他值
resFn(returnVal);
};
};
})
}
};
сюда,MyPromise
Объект уже поддерживает связанные вызовы, тестовый пример:4 链式调用--resolve
. Но, очевидно, мы еще не закончилиreject
Связанные вызовы в состояние.
Идея обработки аналогична, в определенииerrBack
функция, проверкаonRejected
Содержит ли возвращаемый результатthen
методы, о которых идет речь отдельно. Стоит отметить, что если возвращаемое значение является нормальным значением, его следует вызыватьresFn
, вместоrejFn
, потому что это возвращаемое значение принадлежит новомуMyPromise
объект, состояние которого не зависит от текущегоMyPromise
Определяется состояние объекта. То есть возвращается обычное значение, не указанноеreject
состояние, мы по умолчаниюresolve
условие.
Код слишком длинный, только показать часть изменений.
then(onFulfilled, onRejected) {
return new MyPromise((resFn, rejFn) => {
if (this.status === 'success') {
handle(...this.__succ_res);
} else if (this.status === 'error') {
errBack(...this.__err_res); //-+
} else {
this.__queue.push({resolve: handle, reject: errBack}); //-+
};
function handle(value) {
//then方法的onFulfilled有return时,使用return的值,没有则使用保存的值
let returnVal = onFulfilled instanceof Function && onFulfilled(value) || value;
//如果onFulfilled返回的是新MyPromise对象或具有then方法对象,则调用它的then方法
if (returnVal && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {//其他值
resFn(returnVal);
};
};
function errBack(reason) { //++
if (onRejected instanceof Function) {
//如果有onRejected回调,执行一遍
let returnVal = onRejected(reason);
//执行onRejected回调有返回,判断是否thenable对象
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
//无返回或者不是thenable的,直接丢给新对象resFn回调
resFn(returnVal); //resFn,而不是rejFn
};
} else {//传给下一个reject回调
rejFn(reason);
};
};
})
}
Сейчас,MyPromise
Объекты уже хорошо поддерживают цепочку вызовов, тестовый пример:
4 链式调用--resolve
5 链式调用--reject
28 then回调返回Promise对象(reject)
29 then方法reject回调返回Promise对象
Четвертый шаг, реализация методов MyPromise.resolve() и MyPromise.reject()
потому что другие методыMyPromise.resolve()
Метод имеет зависимости, поэтому сначала реализуйте этот метод.
сначала полностью понятьMyPromise.resolve()
Особенности метода, изученного учителем Жуань Ифэном.Начало работы с ECMAScript 6дляMyPromise.resolve()
В части описания метода известно, что функция этого метода очень проста, которая заключается в преобразовании параметра вMyPromise
Объект, ключевой момент кроется в форме параметров, а именно:
- параметр является
MyPromise
пример; - параметр является
thenable
объект; - параметр не имеет
then
объект метода или вообще не объект; - без всяких параметров.
Идея обработки такова:
- Сначала рассмотрим крайние случаи, когда параметр не определен или равен нулю, и непосредственно обработаем передачу исходного значения;
- Во-вторых, параметры
MyPromise
например, никакой обработки не требуется; - Тогда параметры другие
thenable
объект, назовите егоthen
метод, передавая соответствующее значение новомуMyPromise
обратный вызов объекта; - Наконец, это обработка обычных значений.
MyPromise.reject()
Метод относительно прост. а такжеMyPromise.resolve()
разные методы,MyPromise.reject()
Параметры метода будут переданы как естьreject
Причина становится параметром последующего метода.
MyPromise.resolve = (arg) => {
if (typeof arg === 'undefined' || arg == null) {//无参数/null
return new MyPromise((resolve) => {
resolve(arg);
});
} else if (arg instanceof MyPromise) {
return arg;
} else if (arg['then'] instanceof Function) {
return new MyPromise((resolve, reject) => {
arg.then((res) => {
resolve(res);
}, err => {
reject(err);
});
});
} else {
return new MyPromise(resolve => {
resolve(arg);
});
}
};
MyPromise.reject = (arg) => {
return new MyPromise((resolve, reject) => {
reject(arg);
});
};
Есть 8 тестовых случаев:18-25
, вы можете играть, если вам интересно.
Пятый шаг, реализация методов MyPromise.all() и MyPromise.race()
MyPromise.all()
метод получает кучуMyPromise
Объекты, обратный вызов выполнен только тогда, когда они оба успешны. полагатьсяMyPromise.resolve()
метод поставить неMyPromise
Параметры преобразуются вMyPromise
объект.
каждый объект выполняетthen
метод, сохранить результаты в массиве, когда они все будут выполнены, то естьi === arr.length
, перед вызовомresolve()
Обратный вызов, передача результата.
MyPromise.race()
Метод также аналогичен, разница в том, что здесь делаетсяdone
Флаги, если один из них изменит состояние, другие изменения приниматься не будут.
MyPromise.all = (arr) => {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise(function(resolve, reject) {
let i = 0, result = [];
next();
function next() {
//如果不是MyPromise对象,需要转换
MyPromise.resolve(arr[i]).then(res => {
result.push(res);
i++;
if (i === arr.length) {
resolve(result);
} else {
next();
};
}, reject);
};
})
};
MyPromise.race = arr => {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise((resolve, reject) => {
let done = false;
arr.forEach(item => {
//如果不是MyPromise对象,需要转换
MyPromise.resolve(item).then(res => {
if (!done) {
resolve(res);
done = true;
};
}, err => {
if (!done) {
reject(err);
done = true;
};
});
})
})
}
Прецедент:
6 all方法
26 race方法测试
Шестой шаг, реализация методов Promise.prototype.catch() и Promise.prototype.finally()
Они по существуthen
Расширение метода, обработка особых случаев.
Часть комментария в коде catch — это мое оригинальное решение: при запуске catch, если он уже находится в состоянии ошибки, запустить обратный вызов напрямую; если он находится в других состояниях, поместите функцию обратного вызова в очередь событий и выполните ее, когда предыдущее состояние отклонения, наконец, получено; поскольку catch напрямую получает состояние отклонения, разрешение в очереди является пустой функцией, чтобы предотвратить сообщение об ошибке.
Я смотрел это позжеСправочная статья 3Я понял, что есть лучший способ написать это, поэтому я заменил его.
class MyPromise {
constructor(fn) {
//...略
}
then(onFulfilled, onRejected) {
//...略
}
catch(errHandler) {
// if (this.status === 'error') {
// errHandler(...this.__err_res);
// } else {
// this.__queue.push({resolve: () => {}, reject: errHandler});
// //处理最后一个Promise的时候,队列resolve推入一个空函数,不造成影响,不会报错----如果没有,则会报错
// };
return this.then(undefined, errHandler);
}
finally(finalHandler) {
return this.then(finalHandler, finalHandler);
}
};
Прецедент:
7 catch测试
16 finally测试——异步代码错误
17 finally测试——同步代码错误
Шаг седьмой, захват ошибок кода
В настоящее время нашcatch
У него пока нет возможности отлавливать ошибки кода. Подумайте, откуда берется неправильный код? Это должен быть код пользователя. Два источника:
-
MyPromise
обратный вызов конструктора объекта -
then
2 обратных вызова для метода Способ обнаружения ошибок запуска кода является роднымtry...catch...
, поэтому я использую его, чтобы обернуть эти обратные вызовы для запуска и соответствующим образом обработать обнаруженные ошибки.
Для обеспечения ясности кода извлекаются
resolver
,rejecter
Две функции, т.к. они написаны на es5, нужно обрабатывать вручнуюthis
указать на проблему
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//定义function需要手动处理this指向问题
let _this = this; //++
function resolver(...arg) { //++
_this.__succ_res = arg;
_this.status = 'success';
_this.__queue.forEach(json => {
json.resolve(...arg);
});
};
function rejecter(...arg) { //++
_this.__err_res = arg;
_this.status = 'error';
_this.__queue.forEach(json => {
json.reject(...arg);
});
};
try { //++
fn(resolver, rejecter); //-+
} catch(err) { //++
this.__err_res = [err];
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...err);
});
};
}
then(onFulfilled, onRejected) {
//箭头函数绑定了this,如果使用es5写法,需要定义一个替代的this
return new MyPromise((resFn, rejFn) => {
function handle(value) {
//then方法的onFulfilled有return时,使用return的值,没有则使用回调函数resolve的值
let returnVal = value; //-+
if (onFulfilled instanceof Function) { //-+
try { //++
returnVal = onFulfilled(value);
} catch(err) { //++
//代码错误处理
rejFn(err);
return;
}
};
if (returnVal && returnVal['then'] instanceof Function) {
//如果onFulfilled返回的是新Promise对象,则调用它的then方法
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
resFn(returnVal);
};
};
function errBack(reason) {
//如果有onRejected回调,执行一遍
if (onRejected instanceof Function) {
try { //++
let returnVal = onRejected(reason);
//执行onRejected回调有返回,判断是否thenable对象
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
//不是thenable的,直接丢给新对象resFn回调
resFn(returnVal);
};
} catch(err) { //++
//代码错误处理
rejFn(err);
return;
}
} else {//传给下一个reject回调
rejFn(reason);
};
};
if (this.status === 'success') {
handle(...this.__succ_res);
} else if (this.status === 'error') {
errBack(...this.__err_res);
} else {
this.__queue.push({resolve: handle, reject: errBack});
};
})
}
};
Прецедент:
11 catch测试——代码错误捕获
12 catch测试——代码错误捕获(异步)
13 catch测试——then回调代码错误捕获
14 catch测试——代码错误catch捕获
Среди них 12-й тест на ошибку асинхронного кода, результат показывает, что об ошибке сообщается напрямую, ошибка не обнаруживается, собственныйPromise
Тоже самое, я как-то не понимаю, почему его не захватывают, чтобы обращаться с ним.
Восьмой шаг: обработайте состояние MyPromise, чтобы определить, что его нельзя изменить снова.
ЭтоPromise
Одной из ключевых особенностей , с которой несложно справиться, добавление оценки состояния при выполнении обратного вызова, если он уже находится в состоянии успеха или сбоя, код обратного вызова не будет запущен.
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//箭头函数绑定了this,如果使用es5写法,需要定义一个替代的this
let _this = this;
function resolver(...arg) {
if (_this.status === 'pending') { //++
//如果状态已经改变,不再执行本代码
_this.__succ_res = arg;
_this.status = 'success';
_this.__queue.forEach(json => {
json.resolve(...arg);
});
};
};
function rejecter(...arg) {
if (_this.status === 'pending') { //++
//如果状态已经改变,不再执行本代码
_this.__err_res = arg;
_this.status = 'error';
_this.__queue.forEach(json => {
json.reject(...arg);
});
};
};
try {
fn(resolver, rejecter);
} catch(err) {
this.__err_res = [err];
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...err);
});
};
}
//...略
};
Прецедент:
27 Promise状态多次改变
Девятый шаг, методы onFulfilled и onRejected выполняются асинхронно.
До сих пор, если выполняется следующий код,
function test30() {
function fn30(resolve, reject) {
console.log('running fn30');
resolve('resolve @fn30')
};
console.log('start');
let p = new MyPromise(fn30);
p.then(res => {
console.log(res);
}).catch(err => {
console.log('err=', err);
});
console.log('end');
};
Результат:
//MyPromise结果
// start
// running fn30
// resolve @fn30
// end
//原生Promise结果:
// start
// running fn30
// end
// resolve @fn30
Два результата отличаются, потому что методы onFulfilled и onRejected не выполняются асинхронно, и необходимо выполнить следующую обработку, а их коды помещаются в конец текущего раунда очередей задач для выполнения.
function MyPromise(callback) {
//略……
var _this = this;
function resolver(res) {
setTimeout(() => { //++ 利用setTimeout调整任务执行队列
if (_this.status === PENDING) {
_this.status = FULFILLED;
_this.__succ__res = res;
_this.__queue.forEach(item => {
item.resolve(res);
});
};
}, 0);
};
function rejecter(rej) {
setTimeout(() => { //++
if (_this.status === PENDING) {
_this.status = REJECTED;
_this.__err__res = rej;
_this.__queue.forEach(item => {
item.reject(rej);
});
};
}, 0);
};
//略……
};
Прецедент:
30 then方法的异步执行
Выше приведены все мои идеи и процессы написания кода. Полный код и тестовый код дляgithubскачать