Некоторое время назад я использовал две статьи, чтобы подробно объяснить концепцию асинхронности и лежащий в основе цикла событий, а затем я также рассказал о модели публикации-подписки, которая реализует асинхронность самостоятельно:
Прочитайте и поймите исходный код EventEmitter Node.js из модели публикации-подписки.
В этой статье будет рассказано о другой более современной асинхронной реализации:Promise
. Обещание почти обязательно для интервью, поэтому мы можем не только использовать его, но и знать его основополагающие принципы.Лучший способ изучить его принципы — выполнить обещание самим. Итак, в этой статье будет реализовано продолжениеPromise/A+
Канонические обещания. После реализации нам также нужно использоватьPromise/A+
Для проверки правильности нашей реализации используется официальный тестовый инструмент, который содержит в общей сложности 872 тестовых примера.Promise/A+
спецификации, вот ссылка на них:
Promise/A+
Технические характеристики:GitHub.com/promises-AP…
Promise/A+
инструменты для тестирования:GitHub.com/promises-AP…
Полный код этой статьи размещен на GitHub:GitHub.com/Денис — см....
Обещание использования
В Интернете есть много основных способов использования обещаний. Я кратко упомяну их здесь. Я все еще использую в качестве примера три взаимозависимых сетевых запроса. Если у нас есть три сетевых запроса, запрос 2 должен зависеть от результата запроса 1, а запрос 3 должно зависеть от запроса 2. В результате, если вы используете коллбэки, будет три уровня, и вы попадете в «ад колбэков».Использование промиса намного понятнее:
const request = require("request");
// 我们先用Promise包装下三个网络请求
// 请求成功时resolve这个Promise
const request1 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request1 success');
}
});
});
return promise;
}
const request2 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request2 success');
}
});
});
return promise;
}
const request3 = function() {
const promise = new Promise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request3 success');
}
});
});
return promise;
}
// 先发起request1,等他resolve后再发起request2,
// 然后是request3
request1().then((data) => {
console.log(data);
return request2();
})
.then((data) => {
console.log(data);
return request3();
})
.then((data) => {
console.log(data);
})
В приведенном выше примереthen
Его можно вызывать по цепочке, последнийthen
может получить фронтresolve
Что касается выводимых данных, мы видим, что в консоли последовательно печатаются три успеха:
Обещания/спецификация A+
В приведенном выше примере мы уже знаем, как выглядит промис, спецификация Promises/A+ на самом деле является дополнительной спецификацией этого внешнего вида. Ниже я немного поясню об этой спецификации.
период, термин
promise
: это владениеthen
Объект или функция методов, поведение которых соответствует этой спецификации.
thenable
: это определениеthen
метод объекта или функции. В основном это используется для обеспечения совместимости с некоторыми старыми реализациями Promise, если реализация Promise доступна, т. е. имеетthen
метод, он может быть совместим с Promises/A+.
value
:Ссылаться наreslove
Выходным значением может быть любое допустимое значение JS (включаяundefined
, затемное и обещание и т. д.)
exception
: Исключение, используемое в Promisethrow
брошенное значение
reason
: Причина отказа, даreject
Параметры, переданные внутри, указывают, чтоreject
причина
Состояние обещания
Обещания имеют в общей сложности три состояния:
pending
: обещание находится в этом состоянии до того, как оно будет разрешено или отклонено.fulfilled
: после того, как обещание разрешено, оно находится вfulfilled
состояние, которое больше не может быть изменено и должно иметьнеизменныйзначение (value
).rejected
: после того, как обещание отклонено, оно находится вrejected
состояние, которое больше не может быть изменено и должно иметьнеизменныйпричина отказа(reason
).
Обратите внимание, что здесьнеизменныйОтносится===
, то есть еслиvalue
илиreason
Это объект, до тех пор, пока ссылка гарантированно остается неизменной, спецификация не предписывает, чтобы свойства в нем оставались неизменными. Состояние обещания на самом деле очень простое, рисование картинки:
затем метод
Обещание должно иметьthen
способ получить доступ к его значению или отклонить причину.then
Метод имеет два параметра:
promise.then(onFulfilled, onRejected)
необязательный параметр
onFulfilled
а такжеonRejected
являются необязательными параметрами.
- если
onFulfilled
это не функция, ее нужно игнорировать - если
onRejected
это не функция, ее нужно игнорировать
onFulfilled
характеристика
еслиonFulfilled
это функция:
- когда
promise
Он должен быть вызван после выполнения, его первый параметрpromise
окончательная стоимостьvalue
- существует
promise
Его нельзя вызвать, пока не завершится выполнение - Его нельзя вызывать более одного раза
onRejected
характеристика
еслиonRejected
это функция:
- когда
promise
Он должен быть вызван после отклонения выполнения, его первый параметрpromise
причинаreason
- существует
promise
Его нельзя вызвать, пока выполнение не будет отклонено - Его нельзя вызывать более одного раза
несколько вызовов
then
методы могут быть использованы одним и тем жеpromise
звонить несколько раз
- когда
promise
При успешном выполнении всеonFulfilled
Он должен быть отозван по порядку в соответствии с порядком его регистрации. - когда
promise
При отказе всеonRejected
Он должен быть отозван по порядку в соответствии с порядком его регистрации.
вернуть
then
метод должен возвращатьpromise
объект.
promise2 = promise1.then(onFulfilled, onRejected);
- если
onFulfilled
илиonRejected
вернуть значениеx
, затем запуститеПроцесс разрешения обещаний:[[Resolve]](promise2, x)
- если
onFulfilled
илиonRejected
бросить исключениеe
,ноpromise2
Должен отказаться от исполнения и вернуть причину отказаe
- если
onFulfilled
не является функцией иpromise1
успешно выполнен,promise2
Должен успешно выполняться и возвращать то же значение - если
onRejected
не является функцией иpromise1
отказаться от исполнения,promise2
Должен отклонить выполнение и вернуть ту же причину
Большая часть спецификации объясняетПроцесс разрешения обещанийДа, просто смотреть на спецификации очень пусто.Предыдущие спецификации уже могут подтолкнуть нас к написанию собственного промиса.Процесс разрешения обещанийОб этом мы подробно напишем позже.
Напишите обещание самостоятельно
Если мы хотим сами написать промис, нам обязательно нужно знать, какая работа должна быть сделана.Давайте сначала проследим за использованием промиса, чтобы увидеть, что нужно сделать:
- Необходимо использовать новое обещание
new
ключевое слово, то он должен вызываться объектно-ориентированным способом, а Promise — это класс.Более подробное объяснение объектной ориентации в JS см. в этой статье.- нас
new Promise(fn)
Когда вам нужно передать функцию, указав, что параметр Promise является функцией- конструктор передан в
fn
получитresolve
а такжеreject
Две функции, используемые для индикации успеха и неудачи Promise, указывающие на то, что конструктору также требуетсяresolve
а такжеreject
Эти две функции, роль этих двух функций заключается в изменении состояния промиса.- Согласно спецификации промисы имеют
pending
,fulfilled
,rejected
Три состояния, начальное состояниеpending
,передачаresolve
изменил бы его наfulfilled
,передачаreject
будет изменен наrejected
.- После создания объекта экземпляра обещания его можно вызвать
then
Метод, и его можно назвать цепнымthen
Метод, объяснениеthen
является методом экземпляра.Реализация цепного вызова подробно объясняется в этой статье, и я не буду повторяться здесь.. Проще говоряthen
Метод также должен возвращатьthen
Объект метода, который может быть this или новым экземпляром обещания.
Конструктор
Для лучшей совместимости у нас нет ES6.
// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
}
resolve
а такжеreject
метод
Согласно спецификации,resolve
Метод заключается в изменении статуса на выполнено,reject
заключается в изменении статуса на отклонено.
// 这两个方法直接写在构造函数里面
function MyPromise(fn) {
// ...省略前面代码...
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if(that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
}
}
// reject方法参数是reason
function reject(reason) {
if(that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
}
}
}
вызов параметров конструктора
наконецresolve
а такжеreject
Вызовите входящие параметры как параметры, не забудьте добавитьtry
, если обнаружена ошибкаreject
.
function MyPromise(fn) {
// ...省略前面代码...
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
then
метод
Согласно нашему предыдущему анализу,then
Методы можно связывать в цепочку, поэтому он экземплярный метод, а API в спецификацииpromise.then(onFulfilled, onRejected)
, давайте сначала построим полку:
MyPromise.prototype.then = function(onFulfilled, onRejected) {}
Этоthen
Что надо сделать в методе, собственно спецификация нам и говорит, сначала проверьтеonFulfilled
а такжеonRejected
Является ли это функцией, если это не функция, игнорируйте их.Так называемое «игнорирование» не означает ничегонеделания.onFulfilled
«Игнорировать» означаетvalue
вернуть в целости и сохранностиonRejected
возвращатьreason
,onRejected
Поскольку это неправильная ветвь, мы возвращаемсяreason
должен выдать ошибку:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
var realOnFulfilled = onFulfilled;
if(typeof realOnFulfilled !== 'function') {
realOnFulfilled = function (value) {
return value;
}
}
// 如果onRejected不是函数,给一个默认函数,返回reason的Error
var realOnRejected = onRejected;
if(typeof realOnRejected !== 'function') {
realOnRejected = function (reason) {
throw reason;
}
}
}
После того, как параметры проверены, пришло время заняться реальным делом.Подумайте о том, когда мы используем промис, если операция обещания прошла успешно, мы вызовемthen
внутриonFulfilled
, если он потерпит неудачу, он вызоветonRejected
. В соответствии с нашим кодом мы должны проверить статус промиса, если онFULFILLED
, просто позвониonFulfilled
,еслиREJECTED
, просто позвониonRejected
:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ...省略前面代码...
if(this.status === FULFILLED) {
onFulfilled(this.value)
}
if(this.status === REJECTED) {
onRejected(this.reason);
}
}
Подумайте еще раз, когда мы создаем новое обещание, мы можем использовать его прямо так:
new Promise(fn).then(onFulfilled, onRejected);
код вышеthen
Он вызывается сразу после создания экземпляра объекта.fn
Внутри асинхронная операция может быть еще не закончена, что егоstatus
ещеPENDING
, что нам делать?В это время мы не должны быть в состоянии приспособиться немедленно.onFulfilled
илиonRejected
, потому чтоfn
Удалось это или нет, неизвестно.А когда будешь знатьfn
Успех или неудача? ответfn
Активно настроен внутриresolve
илиreject
когда. Так что, если на этот разstatus
статус все ещеPENDING
, нам следуетonFulfilled
а такжеonRejected
Два обратных вызова сохраняются и ждут, покаfn
С выводом,resolve
илиreject
Затем снова вызовите соответствующий код. потому что позадиthen
Есть также цепные вызовы, их будет несколькоonFulfilled
а такжеonRejected
, я использую здесь два массива для их хранения и т. д.resolve
илиreject
Когда все методы в массиве вынимаются и выполняются снова:
// 构造函数
function MyPromise(fn) {
// ...省略其他代码...
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if(that.status === PENDING) {
// ...省略其他代码...
// resolve里面将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
function reject(reason) {
if(that.status === PENDING) {
// ...省略其他代码...
// resolve里面将所有失败的回调拿出来执行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
}
// then方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ...省略其他代码...
// 如果还是PENDING状态,将回调保存下来
if(this.status === PENDING) {
this.onFulfilledCallbacks.push(realOnFulfilled);
this.onRejectedCallbacks.push(realOnRejected);
}
}
Приведенный выше метод временного сохранения обратного вызова и его запуска при выполнении условий напоминает мне модель: модель подписки-публикации. Заходим в массив обратного вызоваpush
Функция обратного вызова фактически эквивалентна регистрации события в центре событий.resolve
Это эквивалентно публикации успешного события, всех зарегистрированных событий, а именноonFulfilledCallbacks
Все методы в нем будут вынесены и выполнены, и так же верноreject
Это эквивалентно размещению события неудачи.Дополнительные принципы модели подписки-публикации можно найти здесь..
сделал небольшой шаг
На данный момент мы можем реализовать асинхронные вызовы, простоthen
Возвращаемое значение не было реализовано, а цепочка вызовов еще не может быть реализована, давайте сначала поиграем с этим:
var request = require("request");
var MyPromise = require('./MyPromise');
var promise1 = new MyPromise((resolve) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
resolve('request1 success');
}
});
});
promise1.then(function(value) {
console.log(value);
});
var promise2 = new MyPromise((resolve, reject) => {
request('https://www.baidu.com', function (error, response) {
if (!error && response.statusCode == 200) {
reject('request2 failed');
}
});
});
promise2.then(function(value) {
console.log(value);
}, function(reason) {
console.log(reason);
});
Вывод приведенного выше кода выглядит следующим образом, что соответствует нашим ожиданиям, указывая на то, что пока с нашим кодом все в порядке:
then
Возвращаемое значение
Согласно спецификацииthen
Возвращаемое значение должно быть обещанием. Спецификация также определяет, как должны обрабатываться различные ситуации. Давайте сначала разберемся с несколькими более простыми ситуациями:
- если
onFulfilled
илиonRejected
бросить исключениеe
,ноpromise2
Должен отказаться от исполнения и вернуть причину отказаe
.
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// ... 省略其他代码 ...
// 有了这个要求,在RESOLVED和REJECTED的时候就不能简单的运行onFulfilled和onRejected了。
// 我们需要将他们用try...catch...包起来,如果有错就reject。
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
realOnFulfilled(that.value);
} catch (error) {
reject(error);
}
});
return promise2;
}
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
realOnRejected(that.reason);
} catch (error) {
reject(error);
}
});
return promise2;
}
// 如果还是PENDING状态,也不能直接保存回调方法了,需要包一层来捕获错误
if(this.status === PENDING) {
var promise2 = new MyPromise(function(resolve, reject) {
that.onFulfilledCallbacks.push(function() {
try {
realOnFulfilled(that.value);
} catch (error) {
reject(error);
}
});
that.onRejectedCallbacks.push(function() {
try {
realOnRejected(that.reason);
} catch (error) {
reject(error);
}
});
});
return promise2;
}
}
- если
onFulfilled
не является функцией иpromise1
успешно выполнен,promise2
Должен успешно выполняться и возвращать то же значение
// 我们就根据要求加个判断,注意else里面是正常执行流程,需要resolve
// 这是个例子,每个realOnFulfilled后面都要这样写
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
realOnFulfilled(that.value);
resolve(that.value);
}
} catch (error) {
reject(error);
}
});
return promise2;
}
- если
onRejected
не является функцией иpromise1
отказаться от исполнения,promise2
В исполнении должно быть отказано и возвращена та же причина. Это требование на самом деле в нашем обнаруженииonRejected
Это было сделано, когда это не функция, потому что мы даем это по умолчаниюonRejected
Внутри будет выброшен Error, так что код точно попадет в ловушку. Но для большей интуитивности код все же соответствует спецификации один за другим. Следует отметить, что еслиpromise1
изonRejected
Казнь прошла успешно,promise2
должно бытьresolve
. Код модификации выглядит следующим образом:
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if(typeof onRejected !== 'function') {
reject(that.reason);
} else {
realOnRejected(that.reason);
resolve();
}
} catch (error) {
reject(error);
}
});
return promise2;
}
- если
onFulfilled
илиonRejected
вернуть значениеx
, затем выполните следующееПроцесс разрешения обещаний:[[Resolve]](promise2, x)
. Эта статья на самом деле является первой статьей спецификации, потому что она более хлопотная, поэтому я поместил ее в конец. Реализация нашего кода ранее, по сути, должна была толькоonRejected
илиonFulfilled
успешно выполнено, мы всеresolve promise2
. В дополнение к этому нам также необходимоonRejected
илиonFulfilled
Возвращаемое значение оценивается, и если есть возвращаемое значение,Процесс разрешения обещаний. Мы специально пишем метод для выполненияПроцесс разрешения обещаний. Реализация нашего кода ранее, по сути, должна была толькоonRejected
илиonFulfilled
успешно выполнено, мы всеresolve promise2
, давайте поместим этот процесс в этот метод, чтобы код стал таким, а другие места аналогичны:
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject); // 调用Promise 解决过程
}
} catch (error) {
reject(error);
}
});
return promise2;
}
Процесс разрешения обещаний
Теперь мы должны реализоватьresolvePromise
метод, эта часть спецификации длиннее, поэтому я прямо пишу спецификацию в виде комментария в коде.
function resolvePromise(promise, x, resolve, reject) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MyPromise) {
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
// 这个if跟下面判断then然后拿到执行其实重复了,可有可无
x.then(function (y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 如果 x 为对象或者函数
else if (typeof x === 'object' || typeof x === 'function') {
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 赋值给 then
var then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (typeof then === 'function') {
var called = false;
// 将 x 作为函数的作用域 this 调用之
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
// 名字重名了,我直接用匿名函数了
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
function (y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
function (r) {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
onFulfilled
а такжеonRejected
время исполнения
В спецификации также есть строчка:onFulfilled
а такжеonRejected
Только если стек среды исполнения содержит толькокод платформыможно только позвонить. Это означает, что на практике необходимо обеспечитьonFulfilled
а такжеonRejected
метод выполняется асинхронно и должен бытьthen
Выполняется в новом стеке выполнения после цикла обработки событий, в котором был вызван метод. Итак, когда мы выполняемonFulfilled
а такжеonRejected
следует включать, когдаsetTimeout
Зайти внутрь.
// 这块代码在then里面
if(this.status === FULFILLED) {
var promise2 = new MyPromise(function(resolve, reject) {
// 这里加setTimeout
setTimeout(function() {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if(this.status === REJECTED) {
var promise2 = new MyPromise(function(resolve, reject) {
// 这里加setTimeout
setTimeout(function() {
try {
if(typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
// 这里加setTimeout
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
// 这里加setTimeout
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0)
});
});
return promise2;
}
Проверьте наши обещания
Мы используем официальные инструменты тестирования Promise/A+promises-aplus-testsприходите к намMyPromise
Чтобы протестировать, чтобы использовать этот инструмент, мы должны реализовать статический методdeferred
, официальное определение этого метода выглядит следующим образом:
deferred
: возвращает объект, содержащий { обещание, разрешение, отклонение }
promise
этоpending
государственные обещания
resolve(value)
использоватьvalue
решить вышеуказанноеpromise
reject(reason)
использоватьreason
отвергнуть тот, что вышеpromise
Мы реализуем код следующим образом:
MyPromise.deferred = function() {
var result = {};
result.promise = new MyPromise(function(resolve, reject){
result.resolve = resolve;
result.reject = reject;
});
return result;
}
Затем используйте npm дляpromises-aplus-tests
Загрузите его, а затем настройте package.json для запуска теста:
{
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"test": "promises-aplus-tests MyPromise"
}
}
Я обнаружил яму при запуске теста, вresolvePromise
, если х естьnull
, его тип такжеobject
, вы должны использовать x для прямого разрешения, предыдущий код перейдет кcatch
потомreject
, значит надо проверитьnull
:
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if(x === null) {
return resolve(x);
}
Этот тест имеет в общей сложности 872 варианта использования, и обещание, которое мы написали, полностью прошло их все:
Другие методы обещаний
В официальном обещании ES6 есть много API, например:
Promise.resolve
Promise.reject
Promise.all
Promise.race
Promise.prototype.catch
Promise.prototype.finally
Promise.allSettled
Хотя их нет в Promise/A+, давайте реализуем их и углубим наше понимание. На самом деле, реализовать Promise/A+ раньше для нас проще простого, потому что все эти API — это предыдущая инкапсуляция.
Promise.resolve
Преобразуйте существующий объект в объект Promise, если параметр метода Promise.resolve не является объектом с методом then (также известным как объект thenable), тогда возвращается новый объект Promise, и его состояние выполняется.
MyPromise.resolve = function(parameter) {
if(parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function(resolve) {
resolve(parameter);
});
}
Promise.reject
Возвращает новый экземпляр Promise с отклоненным состоянием. Причина параметра метода Promise.reject будет передана функции обратного вызова экземпляра.
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason);
});
}
Promise.all
Этот метод используется для переноса нескольких экземпляров Promise в новый экземпляр Promise.
const p = Promise.all([p1, p2, p3]);
Promise.all()
Метод принимает массив в качестве параметра,p1
,p2
,p3
Оба являются экземплярами Promise, в противном случае они будут вызваны первыми.Promise.resolve
метод, который преобразует параметр в экземпляр Promise для дальнейшей обработки. Когда все p1, p2 и p3 разрешены, большое обещание разрешается.Если какой-либо из них отклонен, большое обещание отклоняется.
MyPromise.all = function(promiseList) {
var resPromise = new MyPromise(function(resolve, reject) {
var count = 0;
var result = [];
var length = promiseList.length;
if(length === 0) {
return resolve(result);
}
promiseList.forEach(function(promise, index) {
MyPromise.resolve(promise).then(function(value){
count++;
result[index] = value;
if(count === length) {
resolve(result);
}
}, function(reason){
reject(reason);
});
});
});
return resPromise;
}
Promise.race
Применение:
const p = Promise.race([p1, p2, p3]);
Этот метод также оборачивает несколько экземпляров Promise в новый экземпляр Promise. В приведенном выше коде, покаp1
,p2
,p3
Один из экземпляров первым меняет состояние,p
статус меняется соответственно. Возвращаемое значение экземпляра Promise, который изменился первым, передается вp
функция обратного вызова.
MyPromise.race = function(promiseList) {
var resPromise = new MyPromise(function(resolve, reject) {
var length = promiseList.length;
if(length === 0) {
return resolve();
} else {
for(var i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then(function(value) {
return resolve(value);
}, function(reason) {
return reject(reason);
});
}
}
});
return resPromise;
}
Promise.prototype.catch
Promise.prototype.catch
путь.then(null, rejection)
или.then(undefined, rejection)
Псевдоним для указания функции обратного вызова при возникновении ошибки.
MyPromise.prototype.catch = function(onRejected) {
this.then(null, onRejected);
}
Promise.prototype.finally
finally
Методы используются для указания действий, которые будут выполняться независимо от конечного состояния объекта Promise. Этот метод является стандартным, представленным в ES2018.
MyPromise.prototype.finally = function (fn) {
return this.then(function (value) {
return MyPromise.resolve(fn()).then(function () {
return value;
});
}, function (error) {
return MyPromise.resolve(fn()).then(function () {
throw error
});
});
}
Promise.allSettled
Метод принимает в качестве параметра заданный пример Promise, упакованный в новый экземпляр Promise. Только подождите, пока все эти экземпляры параметров вернут результат, будь тоfulfilled
ещеrejected
, экземпляр оболочки завершится. Метод состоит изES2020Представлять. Новый экземпляр Promise, возвращаемый этим методом, после завершения всегда находится в состоянииfulfilled
, не станетrejected
. состояние становитсяfulfilled
После этого параметр, полученный функцией слушателя Promise, представляет собой массив, каждый член которого соответствует входящемуPromise.allSettled()
Результат выполнения экземпляра Promise.
MyPromise.allSettled = function(promiseList) {
return new MyPromise(function(resolve){
var length = promiseList.length;
var result = [];
var count = 0;
if(length === 0) {
return resolve(result);
} else {
for(var i = 0; i < length; i++) {
(function(i){
var currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then(function(value){
count++;
result[i] = {
status: 'fulfilled',
value: value
}
if(count === length) {
return resolve(result);
}
}, function(reason){
count++;
result[i] = {
status: 'rejected',
reason: reason
}
if(count === length) {
return resolve(result);
}
});
})(i)
}
}
});
}
полный код
Полная версия кода длиннее, если вам здесь не ясно, вы можете перейти на мой GitHub, чтобы увидеть:
// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if (that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve里面将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
// reject方法参数是reason
function reject(reason) {
if (that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
// resolve里面将所有失败的回调拿出来执行
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
function resolvePromise(promise, x, resolve, reject) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MyPromise) {
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
// 这个if跟下面判断then然后拿到执行其实重复了,可有可无
x.then(function (y) {
resolvePromise(promise, y, resolve, reject);
}, reject);
}
// 如果 x 为对象或者函数
else if (typeof x === 'object' || typeof x === 'function') {
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if (x === null) {
return resolve(x);
}
try {
// 把 x.then 赋值给 then
var then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (typeof then === 'function') {
var called = false;
// 将 x 作为函数的作用域 this 调用之
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
// 名字重名了,我直接用匿名函数了
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
function (y) {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
function (r) {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
// 后面返回新promise的时候也做了onFulfilled的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
var realOnFulfilled = onFulfilled;
if (typeof realOnFulfilled !== 'function') {
realOnFulfilled = function (value) {
return value;
}
}
// 如果onRejected不是函数,给一个默认函数,返回reason的Error
// 后面返回新promise的时候也做了onRejected的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
var realOnRejected = onRejected;
if (typeof realOnRejected !== 'function') {
realOnRejected = function (reason) {
throw reason;
}
}
var that = this; // 保存一下this
if (this.status === FULFILLED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
if (this.status === REJECTED) {
var promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
return promise2;
}
// 如果还是PENDING状态,将回调保存下来
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (error) {
reject(error);
}
}, 0)
});
});
return promise2;
}
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
MyPromise.resolve = function (parameter) {
if (parameter instanceof MyPromise) {
return parameter;
}
return new MyPromise(function (resolve) {
resolve(parameter);
});
}
MyPromise.reject = function (reason) {
return new MyPromise(function (resolve, reject) {
reject(reason);
});
}
MyPromise.all = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var count = 0;
var result = [];
var length = promiseList.length;
if (length === 0) {
return resolve(result);
}
promiseList.forEach(function (promise, index) {
MyPromise.resolve(promise).then(function (value) {
count++;
result[index] = value;
if (count === length) {
resolve(result);
}
}, function (reason) {
reject(reason);
});
});
});
return resPromise;
}
MyPromise.race = function (promiseList) {
var resPromise = new MyPromise(function (resolve, reject) {
var length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (var i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then(function (value) {
return resolve(value);
}, function (reason) {
return reject(reason);
});
}
}
});
return resPromise;
}
MyPromise.prototype.catch = function (onRejected) {
this.then(null, onRejected);
}
MyPromise.prototype.finally = function (fn) {
return this.then(function (value) {
return MyPromise.resolve(fn()).then(function () {
return value;
});
}, function (error) {
return MyPromise.resolve(fn()).then(function () {
throw error
});
});
}
MyPromise.allSettled = function (promiseList) {
return new MyPromise(function (resolve) {
var length = promiseList.length;
var result = [];
var count = 0;
if (length === 0) {
return resolve(result);
} else {
for (var i = 0; i < length; i++) {
(function (i) {
var currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then(function (value) {
count++;
result[i] = {
status: 'fulfilled',
value: value
}
if (count === length) {
return resolve(result);
}
}, function (reason) {
count++;
result[i] = {
status: 'rejected',
reason: reason
}
if (count === length) {
return resolve(result);
}
});
})(i)
}
}
});
}
module.exports = MyPromise;
Суммировать
До сих пор наш Promise был просто реализован, но мы не являемся нативным кодом и не можем создавать микрозадачи.Если мы должны создавать микрозадачи, мы можем только симулировать их с помощью других API микрозадач, таких какMutaionObserver
илиprocess.nextTick
. Вот несколько ключевых моментов для обзора:
- Обещание на самом деле модель подписки, выпущенная
-
then
метод для ещеpending
Задача собственно в том, чтобы вызвать callback-функциюonFilfilled
а такжеonRejected
забиты в два массива - Внутри конструктора промисов
resolve
метод преобразует массивonFilfilledCallbacks
Все методы в нем вынуты и выполнены.Вот успешный callback, который был вставлен в метод then раньше. - Точно так же в конструкторе Promise
reject
метод преобразует массивonRejectedCallbacks
Все методы в нем вынуты и выполнены.Вот обратный вызов с ошибкой, который был вставлен в метод then раньше. -
then
Метод возвращает новое обещание для выполнения цепочки -
catch
а такжеfinally
Каждый из этих методов экземпляра должен возвращать новый экземпляр Promise, чтобы связать вызовы.
В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~
Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…
Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....