Введение
Эта статья написана для тех, кто имеет некоторый опыт использования промисов, если вы не использовали промисы, то эта статья может вам не подойти.Понимание использования промисов
Общая картина общей архитектуры этой статьи выглядит следующим образом.Далее мы будем реализовывать шаг за шагом.Promise
.
Класс обещания
Прежде всего, обещание должно быть классом, и оно также определяетresolve
а такжеreject
метод.
function Promise(executor) {
// 初始化state为等待态
this.state = 'pending';
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;
// 存放 fn1 的回调
this.fn1Callbacks = [];
// 存放 fn2 的回调
this.fn2Callbacks = [];
// 成功
let resolve = () => { };
// 失败
let reject = () => { };
// 立即执行
executor(resolve, reject);
}
Приведенный выше код реализуетPromise
Тело конструктора, но с двумя проблемами:
-
executor
Могут быть ошибки, верно, в конце концов, это метод, переданный пользователем, подобный следующему. Если исполнитель делает ошибку, нам нужно использовать try catch, чтобы поймать ошибку, а Promise должен быть отклонен значением его throw:new Promise(function(resolve, reject) { console.log(a) // a 没有被定义 })
-
resolve
,reject
Это все еще пустая функция, нам нужно добавить в нее логику.
Далее продолжаем улучшать:
function Promise(executor){
// 初始化state为等待态
this.state = 'pending';
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;
let resolve = value => {
// state改变,resolve调用就会失败
if (this.state === 'pending') {
// resolve调用后,state转化为成功态
this.state = 'fulfilled';
// 储存成功的值
this.value = value;
}
};
let reject = reason => {
// state改变,reject调用就会失败
if (this.state === 'pending') {
// reject调用后,state转化为失败态
this.state = 'rejected';
// 储存失败的原因
this.reason = reason;
}
};
// 如果executor执行报错,直接执行reject
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
Подытожу картинкой:
Приведенный выше код не особенно сложен, следующийthen
Метод немного сложный.
Реализовать метод then
Promise
объект имеетthen
Метод, используемый для регистрации обратного вызова после определения состояния этого промиса. когдаPromise
Состояние изменения было изменено, независимо от того, успешно оно выполнено или нет, оно будет вызваноthen
метод
then
Метод используется следующим образом:
// then 方法传入两个方法作为参数,一个是fn1方法,一个是 fn2 方法
p1.then(function fn1(data){
// fn1 方法的参数,用于获取promise对象的值
}, function fn2(err){
// fn1 方法的参数,用于获取失败的原因
})
Из вышеприведенного примера ясно, что мы заключаем:
-
then
метод можно найти вp1
вызвал экземпляр. следовательноthen
Реализация метода находится вPromise
изprototype
начальство. -
then
метод вернетPromise
, И вернуть новый промис (Подробности) объект. -
Можно вызывать несколько раз
then
метод, то есть связанные вызовы, и каждый раз новыйPromise
объект,Promise
Статус не определен и может бытьfullfilled
, может быть такжеresolve
, в зависимости от того, какой вызовthen
час,fn1
Возвращаемое значение.
так,then
Реализация метода также очень проста, согласноPromise
состояние для вызова различных функций обратного вызова
Вот идея тогдашнего метода:
Давайте реализуемthen
метод:
// then方法接收两个参数,fn1,fn2,分别为Promise成功或失败后的回调
Promise.prototype.then = function(fn1, fn2) {
var self = this
var promise2
// 首先对入参 fn1, fn2做判断
fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
//todo
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
//todo
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
// todo
})
}
}
Во-первых, для входных параметровfn1
, fn2
выносить суждения. В спецификации сказано,fn1
а такжеfn2
являются необязательными параметрами.
То есть его можно пройти, а можно и не пройти. Входящая функция обратного вызова не является функцией, что мне делать? Спецификация говорит, что просто игнорируйте это. Следовательно, необходимо оценить тип функции обратного вызова и выполнить ее, если это явно функция.
Второй,Promise
Всего возможных состояний три, делим на триif
блок для обработки, каждый из которых возвращаетnew Promise。
Итак, следующая логика:
-
если
promise
статусresolved
, нужно выполнитьfn1
; -
если
promise
статусrejected
, нужно выполнитьfn2
; -
если
promise
статусpending
, мы не уверены, что вызовfn1
ещеfn2
, вы можете сначала сохранить методы только вfn1Callback
,fn2Callback
в массиве. Подождите, пока не будет определено состояние промиса, прежде чем его обрабатывать.
В соответствии с приведенной выше логикой заполните следующий код:
Promise.prototype.then = function(fn1, fn2) {
var self = this
var promise2
fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
// 把 fn1、fn2 放在 try catch 里面,毕竟 fn1、fn2 是用户传入的,报错嘛,很常见
try {
var x = fn1(self.data)
// fn1 执行后,会有返回值,通过 resolve 注入到 then 返回的 promise 中
resolve(x)
} catch (e) {
reject(e)
}
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = fn2(self.data)
reject(x)
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
this.fn1Callback.push(function(value){
try {
var x = fn1(self.data);
resolve(x)
} catch (e) {
reject(e)
}
})
this.fn2Callback.push(function(value) {
try {
var x = fn2(self.data);
reject(x)
} catch (e) {
reject(e)
}
})
})
}
}
-
fn1
,fn2
Все это передается пользователем и может сообщить об ошибке, поэтому его следует поместить в try catch. -
fn1
,fn2
Возвращаемое значение мы записываем какx
, нейминг в спецификации тожеx
, быть последовательным.x
Это значение будет часто использоваться в дальнейшем.
then
Суть функции в том, чтобыfn1
возвращаемое значение, заключенное вpromise
Вернитесь назад. Проблема в том,fn1
Возвращаемое значение записывается разработчиком и может быть странным. В приведенном выше коде предполагается, чтоx
является обычным значением. На самом деле, на самом деле,x
Бывают разные ситуации, с ними приходится разбираться отдельно:
-
если
x
является общим значением, как код выше, используйте его напрямуюresolve
метод,then
может вернуть нормальное обещаниеreturn new Promise((resolve) => { var x = fn1(self.data); resolve(x) })
-
если
x
это обещание, нужно дождаться этогоpromise
изменение состояния, получитьfullfilled
ценность . Затем мы снова меняем код и добавляем суждениеreturn new Promise((resolve) => { var x = fn1(self.data); if (x instanceof Promise) { x.then((data) => {resolve(data)}, (e) => {reject(e)}) } else { resolve(x) } })
-
Согласно правилам, мы должны быть совместимы со всеми видами стилей письма, например, если
x
является объектом, и объект имеетthen
метод, так называемыйthenable
объект, мы должны иметь дело с ним следующим образом:return new Promise((resolve) => { var x = fn1(self.data); if (x instanceof Promise) { x.then((data) => {resolve(data)}, (e) => {reject(e)}) } else if (typeof x.then === 'function'){ x.then(function(y){ resolve(y) }, function(e){ reject(e) }) } else { resolve(x) } })
Выше мы добавили некоторую логику для обработки различных случаев, когда x возвращает значение. Нам нужно перенести эту логику вresolvePromise
метод,resolvePromise
Отвечает за размещение всех видов причудливыхx
упаковано как обычноpromise
.
resolvePromise
resolvePromise
метод, чтобыx
завернутый в обычное обещание
function resolvePromise(promise2, x, resolve, reject) {
// 为了防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}
// 如果 x 是 promise
if (x instanceof Promise) {
x.then(function (data) {
resolve(data)
}, function (e) {
reject(e)
});
return;
}
// 如果 x 是 object 类型或者是 function
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
// 拿x.then可能会报错
try {
// 先拿到 x.then
var then = x.then;
var called
if (typeof then === 'function') {
// 这里的写法,是 then.call(this, fn1, fn2)
then.call(x, (y) => {
// called 是干什么用的呢?
// 有一些 promise 实现的不是很规范,瞎搞的,比如说,fn1, fn2 本应执行一个,
// 但是有些then实现里面,fn1, fn2都会执行
// 为了 fn1 和 fn2 只能调用一个, 设置一个 called 标志位
if (called) {
return;
}
called = true;
return resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) {
return;
}
called = true;
return reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) {
return;
}
return reject(e);
}
} else {
resolve(x);
}
}
В приведенном выше коде необходимо обратить внимание на:
-
var then = x.then
Эта строка кода может сообщать об ошибке, поэтому вам нужно обернуть ее с помощью try catch. Почему можно получить ошибку при выборке свойств объекта?Promise
Реализаций много(bluebird
, Q и т. д.), Promises/A+ — это просто спецификация, и все реализуют Promises в соответствии с этой спецификацией, чтобы быть универсальными, поэтому необходимо учитывать все возможные ошибки, предполагая, что объект Promise, реализованный другим человеком, используетObject.defineProperty()
Злонамеренно выдавая ошибку при взятии значения, мы можем предотвратить ошибки в коде. -
Если есть то в объекте, а то есть тип функции, то его можно рассматривать как объект промиса, тогда используйте
x
Вызовите метод then следующим образом. -
если
x === promise2
, это вызовет циклическую ссылку, и если вы дождетесь завершения, будет сообщено об ошибке "циклическая ссылка". Что, если x и promise2 — одно и то же?let p2 = p1.then(function(data){ console.log(data) return p2; })
В приведенном выше примере
p1.then()
Возвращаемое значениеp2
,fn1
Возвращаемое значение такжеp2
. В чем проблема с этим?promise
Если не вызывается вручнуюresolve
метод, нет возможности изменить состояние.p2
Вы не можете изменить свое состояние, вы не можете изменить свое состояние самостоятельно, и вы никогда не будетеfullfilled
,rejected
-
Нам нужны разные реализации Promise, чтобы иметь возможность взаимодействовать друг с другом, т.е.
fn1
/fn2
Возвращаемое значение, x, рассматривается как объект, который может быть Promise, о чем говорится в стандарте.thenable
, и вызовите метод then для x самым безопасным способом. Если все реализуют его по стандарту, то разные промисы могут взаимодействовать друг с другом. Стандарт должен быть на безопасной стороне, даже если x возвращает объект со свойством then, но не соответствует стандарту Promise (например, этот x вызывает оба параметра в своем then, синхронно или асинхронно (PS, в принципе, тогда The два параметра необходимости вызывать асинхронно, о чем будет сказано ниже), либо они вызываются после ошибки, либо тогда вообще не являются функцией), а также могут обрабатываться максимально корректно.
Затем функции должны выполняться асинхронно.
Наконец, мы только что сказали, что в принципеpromise.then(onResolved, onRejected)
Двухфазные функции нужно вызывать асинхронно, по этому поводу в стандарте тоже естьиллюстрировать:
In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.
Так как же превратить синхронный код в асинхронное выполнение? Вы можете использовать функцию setTimeout для ее имитации:
setTimeout(()=>{
//此入的代码会异步执行
},0);
Используя эту технику, вы можете использовать setTimeout, чтобы сделать все места, где код выполняется асинхронно, например:
setTimeout(() => {
try {
let x = fn1(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
},0);
обещанная структура тела
// 1. 定义 status 状态
// 2. fn1, fn2 的数组
// 3. 定义 resolve reject 方法
// 4. executor 执行
function Promise(executor) {
let self = this;
self.status = 'pending';
self.fn1Callback = [];
self.fn2Callback = [];
// resolve 做到事情
// 1. 修改this 实例的状态
// 2. 修改this 这里的data
// 3. 遍历执行 this fn1Callback 上挂载的方法
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject);
}
setTimeout(() => { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for (let i = 0; i < self.fn1Callback.length; i++) {
self.fn1Callback[i](value);
}
}
});
}
function reject(reason) {
setTimeout(() => { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
for (let i = 0; i < self.fn2Callback.length; i++) {
self.fn2Callback[i](reason);
}
}
});
}
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
// 1. 参数校验
// 2. 根据 statue, 执行 fn1, fn2 或者把 执行fn1, fn2的行为保存在数组
// 3. 把 fn1,fn2 的返回值, 使用 resolvePromise 包裹成 promise
Promise.prototype.then = function (fn1, fn2) {
let self = this;
let promise2;
fn1 = typeof fn1 === 'function' ? fn1 : function (v) {
return v;
};
fn2 = typeof fn2 === 'function' ? fn2 : function (r) {
throw r;
};
// 执行到 then, 并不确定 promise 状态已经是 resolved
if (self.status === 'resolved') {
// then() 执行后,返回一个promise, promise 的值
return promise2 = new Promise(((resolve, reject) => {
setTimeout(() => { // 异步执行onResolved
try {
// 执行 fn1(),拿到结果 x
// fn1是用户传入的,那fn1返回值, 可能性可就多了
let x = fn1(self.data);
// 如果 x 是简单值,直接 resolve(x);
// resolve(x);
// 需要使用 resolvePromise 方法封装
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.status === 'rejected') {
return promise2 = new Promise(((resolve, reject) => {
setTimeout(() => { // 异步执行onRejected
try {
let x = fn2(self.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.status === 'pending') {
// 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
return promise2 = new Promise(((resolve, reject) => {
// 先定义一个方法,把方法 挂载到 onResolvedCallback 数组上
// 方法里面 就是 调用传入的 fn1
self.onResolvedCallback.push((value) => {
try {
let x = fn1(value);
resolvePromise(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.onRejectedCallback.push((reason) => {
try {
let x = fn2(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 1. 普通值
// 2. promise 值
// 3. thenable 的值,执行 then
function resolvePromise(promise2, x, resolve, reject) {
// 为了防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}
// 如果 x 是 promise
if (x instanceof Promise) {
x.then(function (data) {
resolve(data)
}, function (e) {
reject(e)
});
return;
}
// 如果 x 是 object 类型或者是 function
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
// 拿x.then可能会报错
try {
// 先拿到 x.then
var then = x.then;
var called
if (typeof then === 'function') {
// 这里的写法,是 then.call(this, fn1, fn2)
then.call(x, (y) => {
// called 是干什么用的呢?
// 有一些 promise 实现的不是很规范,瞎搞的,比如说,fn1, fn2 本应执行一个,
// 但是有些then实现里面,fn1, fn2都会执行
// 为了 fn1 和 fn2 只能调用一个, 设置一个 called 标志位
if (called) {
return;
}
called = true;
return resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) {
return;
}
called = true;
return reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) {
return;
}
return reject(e);
}
} else {
resolve(x);
}
}
Рукописное обещание.все
Promise.all
нужно ждать всехpromise
статус сталfulfilled
только послеresolve
, но пока естьpromise
Если это не удается, он возвращает результат ошибки.
Promise.all = function (arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) {
throw new Error(`argument must be a array`)
}
let dataArr = [];
let num = 0;
for (let i = 0; i < arr.length; i++) {
let p = arr[i];
p.then((data) => {
dataArr.push(data);
num ++;
if (num === arr.length) {
return resolve(data)
}
}).catch((e) => {
return reject(e)
})
}
})
}
Рукописное обещание.повторить попытку
повторная попытка является ошибкой и будет пытаться, и это будет реально только после попытки более определенного количества раз.reject
Promise.retry = function(getData, times, delay) {
return new Promise((resolve, reject) => {
function attemp() {
getData().then((data) => {
resolve(data)
}).catch((err) => {
if (times === 0) {
reject(err)
} else {
times--
setTimeout(attemp, delay)
}
})
}
attemp()
})
}
полный тест
После окончательного написания исходного кода Promise действительно ли он соответствует окончательным требованиям?Обещания/спецификация A+, сообщество открытого исходного кода предоставляет пакет для тестирования нашего кода:promises-aplus-tests
Этот пакет может проверять, соответствует ли код, который мы написали, один за другим.Если какой-либо элемент несовместим, об этом будет сообщено нам.Если вы проверите свой код полностью зеленым, то поздравляем, ваш Proimse уже легальный, вы можете Доступно онлайн для использования другими.
Эта статья была опубликована по ссылке блога github:GitHub.com/Ду Цзюньчэн/…, будет обновляться в течение длительного времени в будущем, добро пожаловать, чтобы обратить внимание на звезду
Кроме того
ByteDance (Ханчжоу|Пекин|Шанхай) набирает много людей, льготы супер, уровень зарплаты НИМ, без часов на работе, ежедневный полдник, неограниченное количество бесплатных закусок, бесплатное трехразовое питание (читаю меню, волосатые краб, морское ушко, морской гребешок, морепродукты на гриле, кусочки рыбы, говяжья вырезка с черным перцем, говядина карри, острые раки), бесплатный тренажерный зал, сенсорная панель начального уровня с 15-дюймовым верхом с новым MBP и ежемесячное пособие на аренду жилья. В этот раз возможностей действительно много.Через год штат НИОКР увеличится в n раз.Техническая атмосфера хорошая,больших коров много,сверхурочных меньше.О чем вы сомневаетесь? Отправьте свое резюме по электронной почте ниже, сейчас!
Это всего лишь небольшой кусочек jd, больше добро пожаловать в WeChat~
Фронтенд JD:job.headline.com/is/B JM4работа…
бэкэнд JD:job.headline.com/is/bj jj ts работа…
тест JD:job.toutiao.com/Yes/B Ноутбук JF V9…
Продукт JD:job.toutiao.com/yes/не JB GV8job…
Стажер фронтенда:job.headline.com/is/BJ6NJ Работа…
Бэкенд-стажер:job.headline.com/is/BJ Diary RK работа…
Продолжать набирать большое количество интерфейсных, серверных, клиентских, тестовых, продуктовых, стажировочных агентств.
Резюме присылайте на dujuncheng@bytedance.com, предлагайте добавить WeChat dujuncheng1, можно поболтать о жизни, укажите, пожалуйста, откуда вы из Наггетса и где хотите разместить