что такое обещание
Так называемый Promise — это просто контейнер, содержащий результат события (обычно асинхронной операции), которое завершится в будущем. Синтаксически Promise — это объект, из которого можно получить сообщение для асинхронной операции. Обещания предоставляют унифицированный API, и различные асинхронные операции могут обрабатываться одинаково.
Промис — это решение для работы с асинхронным кодированием.До появления промисов написание асинхронного кода обрабатывалось функциями обратного вызова.С самой функцией обратного вызова проблем нет, но она усложняется, когда несколько асинхронных обратных вызовов логически связаны.
const fs = require('fs');
fs.readFile('1.txt', (err,data) => {
fs.readFile('2.txt', (err,data) => {
fs.readFile('3.txt', (err,data) => {
//可能还有后续代码
});
});
});
Вышеприведенное читает 3 файла, они представляют собой отношения между слоями, вы можете видеть, что несколько рукавов асинхронного кода разрабатываются не продольно, а горизонтально, будь то из-за грамматики или из-за устранения неполадок, Таким образом, появление Promise может решить эту проблему.
Если приведенный выше код переписать в версию Promise следующим образом:
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
readFile('1.txt')
.then(data => {
return readFile('2.txt');
}).then(data => {
return readFile('3.txt');
}).then(data => {
//...
});
Видно, что код развивался вертикально сверху вниз, что больше соответствует логике людей.
Напишите промис ниже, в соответствии со спецификацией Promises/A+, вы можете обратиться к исходному тексту спецификации:Обещания/спецификация A+
Внедрение Promise вручную – это классический вопрос для собеседования перед интерфейсом. Например, интервью Meituan является обязательным вопросом. Логика Promise относительно сложна, и ее нужно учитывать. Ниже приведены основные моменты написания Promise от руки. и как использовать код для его реализации.
Базовая структура кода обещания
При создании экземпляра объекта Promise в качестве исполнителя передается функция, и есть два параметра (разрешение и отклонение), которые переводят результат в состояние успеха и состояние отказа соответственно. Мы можем написать основную структуру
function Promise(executor) {
this.state = 'pending'; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
function resolve(value) {
}
function reject(reason) {
}
}
module.exports = Promise;
Свойство state содержит состояние объекта Promise.В спецификации указано, что объект Promise имеет только три состояния:Государство ожидания (в ожидании) состояние успеха (разрешено) и состояние неудачи (отклонено). Когда объект Promise выполняется успешно, у него должен быть результат, который сохраняется с помощью свойства value; он также может по какой-то причине дать сбой, а причина сбоя хранится в свойстве Reason.
Метод then определен в прототипе
У каждого экземпляра Promise есть метод then, который используется для асинхронной обработки возвращаемого результата.Это метод, определенный в прототипе.Сначала мы пишем пустой метод для подготовки:
Promise.prototype.then = function (onFulfilled, onRejected) {
};
Выполняется сразу после создания экземпляра промиса
Когда мы создаем промис, его исполнительная функция (Executor) будет выполняться немедленно, это точно:
let p = new Promise((resolve, reject) => {
console.log('执行了');
});
результат операции:
执行了
Поэтому, когда промис создается, входящая функция-исполнитель немедленно вызывается в конструкторе для выполнения.
function Promise(executor) {
var _this = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
executor(resolve, reject); //马上执行
function resolve(value) {}
function reject(reason) {}
}
Он уже находится в состоянии успеха или в состоянии сбоя и больше не может быть обновлен.
В спецификации указано, что когда объект Promise переходит из состояния ожидания в успешное состояние (разрешено) или в состояние сбоя (отклонено), это состояние нельзя изменить снова. Поэтому нам нужно судить при обновлении состояния, если текущее состояние находится в состоянии ожидания (waiting state), его можно обновить:
function resolve(value) {
//当状态为pending时再做更新
if (_this.state === 'pending') {
_this.value = value;//保存成功结果
_this.state = 'resolved';
}
}
function reject(reason) {
//当状态为pending时再做更新
if (_this.state === 'pending') {
_this.reason = reason;//保存失败原因
_this.state = 'rejected';
}
}
Как видно из вышеизложенного, суждения добавляются к функциям разрешения и отклонения соответственно. Только текущее состояние находится в ожидании выполнения операции. В то же время успешный результат и причина сбоя сохраняются к соответствующему атрибуты. Затем установите государственную собственность в обновленное состояние.
Базовая реализация метода then
Когда состояние промиса изменяется, метод then будет вызываться независимо от того, успешно оно или нет.Поэтому реализация метода then также очень проста.Вы можете вызывать разные callback-функции в зависимости от состояния:
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.state === 'resolved') {
//判断参数类型,是函数执行之
if (typeof onFulfilled === 'function') {
onFulfilled(this.value);
}
}
if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
onRejected(this.reason);
}
}
};
Следует отметить, что в спецификации указано, что и onFulfilled, и onRejected являются необязательными параметрами, что означает, что их можно передавать или не передавать. Входящая функция обратного вызова не является функцией, что мне делать? Спецификация говорит, что просто игнорируйте это. Следовательно, необходимо оценить тип функции обратного вызова и выполнить ее, если это явно функция.
Обещания поддерживают асинхронность
Здесь написан код, вроде основные функции реализованы, но есть еще большая проблема.В настоящее время этот промис не поддерживает асинхронный код.Если асинхронная операция инкапсулирована в промис, то метод then не может сделать что-нибудь:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},500);
});
p.then(data => console.log(data)); //没有任何结果
Выполнение приведенного выше кода не дало результатов. Намерение состояло в том, чтобы выполнить метод then через 500 миллисекунд. В чем проблема? Причина в том, что функция setTimeout вызывает асинхронное выполнение resolve с задержкой, когда вызывается метод then, состояние в этот момент все еще находится в ожидании, поэтому метод then не вызывает ни onFulfilled, ни onRejected.
Как решить эту проблему? Мы можем опубликовать модель подписки в реализации метода, если затем все еще ожидающее состояние (ожидающее), поместите функцию обратного вызова на временную массив хранения, когда состояние, подходянно изменилось из массива, чтобы выполнить просто хорошо, очистить эту идею, мы осознаем Он, первые два новых типа массивов на классе массива для выполнения функции обратного вызова:
function Promise(executor) {
var _this = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledFunc = [];//保存成功回调
this.onRejectedFunc = [];//保存失败回调
//其它代码略...
}
Таким образом, при выполнении метода then, если состояние все еще находится в состоянии ожидания, функции обратного вызова по очереди помещаются в массив:
Promise.prototype.then = function (onFulfilled, onRejected) {
//等待态,此时异步代码还没有走完
if (this.state === 'pending') {
if (typeof onFulfilled === 'function') {
this.onFulfilledFunc.push(onFulfilled);//保存回调
}
if (typeof onRejected === 'function') {
this.onRejectedFunc.push(onRejected);//保存回调
}
}
//其它代码略...
};
После регистрации обратного вызова следующим шагом будет его выполнение при изменении состояния:
function resolve(value) {
if (_this.state === 'pending') {
_this.value = value;
//依次执行成功回调
_this.onFulfilledFunc.forEach(fn => fn(value));
_this.state = 'resolved';
}
}
function reject(reason) {
if (_this.state === 'pending') {
_this.reason = reason;
//依次执行失败回调
_this.onRejectedFunc.forEach(fn => fn(reason));
_this.state = 'rejected';
}
}
Пока что Promise поддерживает асинхронные операции, и метод then может быть правильно выполнен для возврата результата после задержки setTimeout.
цепной вызов
Наиболее мощной частью обработки асинхронного кода в Promise является поддержка цепочек вызовов, что также является наиболее сложным.Давайте сначала разберемся, как это определено в спецификации:
- Каждый метод then возвращает новый объект Promise (Основной)
- Если метод then явно возвращает объект Promise, этот объект имеет преимущественную силу, и будет возвращен его результат.
- Если метод then возвращает общее значение (такое как Number, String и т. д.), используйте это значение, чтобы обернуть его в новый объект Promise и вернуть его.
- Если в методе then нет оператора return, он считается возвращающим объект Promise, обернутый с помощью Undefined.
- Если в методе then есть исключение, вызовите метод состояния отказа (reject), чтобы перейти к onRejected следующего then.
- Если метод then не проходит ни в один обратный вызов, он продолжает передаваться вниз (функция передачи значения).
Спецификация очень абстрактная, мы можем использовать код для демонстрации моментов, которые не так просто понять.
Где третий термин, если возвращаемое значение является общим значением, чтобы использовать его упакованным в Promise, мы используем код для демонстрации:
let p =new Promise((resolve,reject)=>{
resolve(1);
});
p.then(data=>{
return 2; //返回一个普通值
}).then(data=>{
console.log(data); //输出2
});
Видно, что когда затем возвращается нормальное значение, то успех следующего состояния в обратном вызове может затем вернуться к результату, тогда это объясняется использованием упакованного обещания для 2, в котором указано соответствие.
Пункт 4, если в методе then нет оператора return, он считается возвращающим объект Promise, обернутый с помощью Undefined
let p = new Promise((resolve, reject) => {
resolve(1);
});
p.then(data => {
//没有return语句
}).then(data => {
console.log(data); //undefined
});
Как вы можете видеть, сообщение об ошибке не будет выдано, когда не будет возвращено никакого значения, а когда нет инструкции, это фактическиreturn undefined;
То есть оберните undefined в объект Promise и передайте его в состояние успеха следующего затем.
Пункт 6, если метод then не проходит ни в один обратный вызов, он продолжает проходить вниз, что это значит? Это проникновение значения в Promise, продемонстрируем его на коде:
let p = new Promise((resolve, reject) => {
resolve(1);
});
p.then(data => 2)
.then()
.then()
.then(data => {
console.log(data); //2
});
В приведенном выше коде два пустых метода then вызываются последовательно после первого метода then, функция обратного вызова не передается и возвращаемое значение не передается.В это время Promise будет передавать значение до тех пор, пока вы не получите и не обработаете его. происходит так называемое проникновение ценностей.
Теперь вы можете понять принцип цепочек вызовов, в любом случае метод then вернет объект Promise, так что будет следующий метод then.
Уяснив эти моменты, мы можем приступить к реализации связанного вызова метода then и улучшить его вместе:
Promise.prototype.then = function (onFulfilled, onRejected) {
var promise2 = new Promise((resolve, reject) => {
//代码略...
}
return promise2;
};
Во-первых, если then в любом случае возвращает объект Promise, мы создаем экземпляр нового promise2 и возвращаем его.
Следующим шагом является создание нового объекта Promise на основе возвращаемого значения предыдущего метода then.Поскольку эта логика сложна и имеет много вызовов, мы извлекаем метод для работы, который также описан в спецификации:
/**
* 解析then返回值与新Promise对象
* @param {Object} promise2 新的Promise对象
* @param {*} x 上一个then的返回值
* @param {Function} resolve promise2的resolve
* @param {Function} reject promise2的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
//...
}
resolvePromise
Метод используется для инкапсуляции результатов, сгенерированных вызовом цепочки, напишем его логику по порядку, во-первых, это указано в спецификации.promise2
а такжеx
Указывая на тот же объект, он терпит неудачу по причине TypeError. Оригинальный текст выглядит следующим образом:
If promise and x refer to the same object, reject promise with a TypeError as the reason.
Что это значит? На самом деле, это круговая ссылка. Когда возвращаемое значение тогда такое же, как вновь сгенерированное объект обещания (ссылочный адрес одинаковы), будет брошен тип:
let promise2 = p.then(data => {
return promise2;
});
результат операции:
TypeError: Chaining cycle detected for promise #<Promise>
Очевидно, что если вы возвращаете свой собственный объект Promise, состояние всегда находится в ожидании, и его больше нельзя разрешить или отклонить, и программа умрет, поэтому с ним нужно разобраться в первую очередь:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Promise发生了循环引用'));
}
}
Следующим шагом является работа с различными ситуациями. когдаx
Это Обещание, затем выполните его, успех есть успех, неудача есть неудача. подобноx
является объектом или функцией и обрабатывается дальше, иначе это обычное значение:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Promise发生了循环引用'));
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是个对象或是函数
} else {
//否则是个普通值
resolve(x);
}
}
В настоящее время в спецификации указано, что если это объект, попробуйте применить метод then к объекту.Если в это время будет сообщено об ошибке, обещание2 будет преобразовано в состояние сбоя. оригинал:
If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
function resolvePromise(promise2, x, resolve, reject) {
//代码略...
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是个对象或是函数
try {
let then = x.then;//取出then方法引用
} catch (e) {
reject(e);
}
} else {
//否则是个普通值
resolve(x);
}
}
Скажем пару слов, зачем брать на себя свойства предмета, скорее всего даваемого? Promise существует множество реализаций (bluebird, Q и т.д.), Promises/A+ это всего лишь спецификация, все мы кликаем спецификации для достижения универсального промиса возможного, поэтому все что может пойти не так нужно учитывать, предполагая объекты промиса реализованные с помощью другого человекObject.defineProperty()
Злонамеренно выдавая ошибку при взятии значения, мы можем предотвратить ошибки в коде.
На этом этапе, если в объекте есть то, а затем есть тип функции, его можно рассматривать как объект Promise, а затем использоватьx
Вызовите метод then следующим образом.
If then is a function, call it with x as this
//其他代码略...
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是个对象或是函数
try {
let then = x.then;
if (typeof then === 'function') {
//then是function,那么执行Promise
then.call(x, (y) => {
resolve(y);
}, (r) => {
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
//否则是个普通值
resolve(x);
}
Таким образом, метод цепной записи в основном завершен. Но есть и крайний случай: если объект промиса преобразуется в успешное состояние или объект промиса передается в случае сбоя, выполнение должно продолжаться до тех пор, пока не будет выполнен окончательный промис.
p.then(data => {
return new Promise((resolve,reject)=>{
//resolve传入的还是Promise
resolve(new Promise((resolve,reject)=>{
resolve(2);
}));
});
})
Первоначальный текст спецификации выглядит следующим образом:
If a promise is resolved with a thenable that participates in a circular thenable chain, such that the recursive nature of [[Resolve]](promise, thenable) eventually causes [[Resolve]](promise, thenable) to be called again, following the above algorithm will lead to infinite recursion. Implementations are encouraged, but not required, to detect such recursion and reject promise with an informative TypeError as the reason.
Очень просто, перепишите вызов resolve для рекурсивного выполнения метода resolvePromise, чтобы он не завершался до тех пор, пока промис не будет разрешен до нормального значения, то есть до завершения этой спецификации:
//其他代码略...
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是个对象或是函数
try {
let then = x.then;
if (typeof then === 'function') {
let y = then.call(x, (y) => {
//递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
//是个普通值,最终结束递归
resolve(x);
}
На этом код цепного вызова завершен. позвоните, где уместноresolvePromise
метод.
последний из последних
На самом деле здесь написан настоящий исходный код Promise, но до 100 баллов ему еще далеко.
В спецификации указано, что метод then класса Promise выполняется асинхронно.
onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
В нативном объекте Promise ES6 это уже реализовано, но наш собственный код выполняется синхронно, я не верю, что мы сможем это попробовать, так как же превратить синхронный код в асинхронное выполнение? Вы можете использовать функцию setTimeout для ее имитации:
setTimeout(()=>{
//此处的代码会异步执行
},0);
Используя эту технику, вы можете использовать setTimeout, чтобы сделать все места, где код выполняется асинхронно, например:
setTimeout(() => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
},0);
Что ж, теперь это полноценный исходный код Promise.
полный тест
После окончательного написания исходного кода Promise действительно ли он соответствует окончательным требованиям?Обещания/спецификация A+, сообщество открытого исходного кода предоставляет пакет для тестирования нашего кода:promises-aplus-tests
Использование этого пакета не является подробной информацией, этот пакет может проверять элемент, мы пишем код на предмет соответствия, если есть какие-либо расхождения, он даст нам отчет, проверьте, полностью ли ваш код зеленый, а затем поздравляем ваш Proimse иметь законный, онлайн, который вы можете предоставить другим, используя:
Пройдено 872 теста!
Теперь исходный код будет написан, и это, наконец, вопрос интервьюера, который может быть уверен.