В последнее время я наблюдаю за Android-проектом частным образом, а фронтенд-часть тщательно не изучала. Когда я вчера писал интерфейсный проект рефакторинга компании, я обнаружил, что как только была асинхронная задача, слово обещание появилось в моей голове в виде условного рефлекса. После большого рефакторинга мое сердце задается вопросом: раз уж промисы так просты в использовании, могу ли я сам написать промис? Я долго думал об этом и смоделировал по своим представлениям, но когда я пообщался с большим начальником, он сказал, что мой метод письма не соответствуетPromise/A+Технические характеристики. В то время я был ошеломлен, и мне всегда хотелось написать об этом вопросе, когда я работал сверхурочно.
1. Что такое обещание?
1.1 Зачем использовать промисы?
Все мы знаем, что весь код в javascript выполняется в одном потоке. Из-за этого недостатка «одного потока» все сетевые запросы с большим количеством javascript, события браузера и т. д. могут выполняться только асинхронно. Все традиционные методы асинхронной реализации реализуются с помощью функций обратного вызова, например:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === RESULT_OK) {
return success(req.responseText);
}
return fail(req.status);
}
}
Хотя это более интуитивно понятно, это не соответствует эстетическим привычкам программистов, а возможность повторного использования кода также очень низка. В соответствии с текущим стилем кода, отличный шаблон кода должен быть в виде связанного списка.
// Android写习惯了,一般封装网络都是okhttp 。不用理我 = =
okhttp.request()
.onSuccesul()
.onFail();
Так появились обещания.
1.2, обещание введения
Китайская интерпретация обещания: обещание. Я думаю, что этот перевод действительно подчеркивает значение объекта.
В обещании есть три состояния:
- в ожидании: в процессе
- выполнено: успешно
- отклонено: закрыто
Почему объяснение обещаний подчеркивает характеристики объектов обещаний? Давайте поговорим о двух особенностях промисов:
1. Объект нельзя изменить никаким образом. Как это объяснить? Пока вы берете на себя обязательство, никакая внешняя среда не может вмешиваться в вас, и вас определяет только результат одного шага операции. 2. Однажды состояние изменилось, оно больше не изменится, и этот результат можно получить в любой момент.
Из вышеперечисленных особенностей это углубляет понимание приверженности!
2. Обещание просто изучить.
Так много было сказано ранее, все это глупо, давайте воспользуемся кодом, чтобы испытать острые ощущения от обещания.
new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
Из приведенного выше кода ясно видно, что Promise получает два параметра: разрешение и отклонение. Роль функции разрешения состоит в том, чтобы изменить состояние объекта Promise с «в процессе» на «успешно» (ожидание => разрешено), вызвать, когда асинхронная операция прошла успешно, и передать результат асинхронной операции в качестве параметра. ; Функция функции reject состоит в том, чтобы изменить состояние объекта Promise с «выполняется» на «сбой» (ожидание => отклонено), вызвать в случае сбоя асинхронной операции и передать сообщение об ошибке асинхронной операции в качестве параметра .
И после того, как экземпляр объекта Promise сгенерирован, его можно использовать для указания разрешенного разрешения и отклонения соответственно. И отклонение в этом случае не является обязательным.
Что касается промисов, я думаю, есть три основных момента, о которых нужно рассказать читателям Давайте взглянем на следующий код:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
})
Мы обнаружили, что если мы запустим только этот фрагмент кода, консоль все равно будет напечатана. Это означает, что промис запускается при его создании. Давайте посмотрим на кусок кода:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
console.log(randomNumber)
}).then(res=>{
console.log(res);
},error=>{
console.log(error)
})
Эффект следующий:
Из приведенного выше явления мы можем легко увидеть, что код после разрешения и отклонения обычно все еще работает.Если вы хотите избежать этой ситуации, вы можете использовать его в сочетании с возвратом, например:return resolve('success!!!');
return reject('fail!!!');
3. Исследование важных свойств обещания
2.1, то
Экземпляры Promise имеют метод then, то есть метод then определен в объекте-прототипе Promise.prototype. Его роль заключается в добавлении функции обратного вызова к экземпляру Promise при изменении состояния. В этом случае есть две функции обратного вызова, первая возвращает функцию обратного вызова разрешения, а последняя является необязательным значением. А затем возвращает новое обещание, так что вы всегда можете использовать структуру цепочки.
2.2 поймать
Catch можно рассматривать как псевдоним then(null/undefined, reject), который специально используется для указания функции обратного вызова при возникновении ошибки. Так зачем проектировать это свойство? Есть два основных аспекта, сначала посмотрите на следующий код:
// 普通then
.then(res=>{
console.log(res);
},error=>{
console.log(error)
})
//catch
.then(res=>{
console.log(res);
})
.catch(error=>{
console.log(error)
})
Очевидно, что второй немного более элегантен, чем предыдущий.
Давайте посмотрим на другой кусок кода:
// 普通then
.then(res=>{
throw Error("have exception")
console.log(res);
},error=>{
console.log(error)
})
// catch
.then(res=>{
throw Error("have exception")
}).catch(error=>{
console.log(error)
})
Запустив приведенный выше код, мы ясно обнаружим, что catch может перехватывать затем исключения, но затем обратные вызовы reject не могут перехватывать разрешающие исключения. Это в некоторой степени обеспечивает нормальный порядок выполнения кода.
2.3, наконец
Наконец, это свойство, представленное в es2018, которое будет выполняться независимо от конечного состояния объекта Promise. Суть его также в характеристиках тогдашнего метода.
promise
.finally(() => {
// ...
});
// 等同于
promise
.then(
result => {
// ...
return result;
},
error => {
// ...
throw error;
}
);
2.4 все
Метод Promise.all используется для переноса нескольких экземпляров Promise в новый экземпляр Promise. Его основной синтаксис:
const p = Promise.all([p1, p2, p3]);
Метод Promise.all принимает в качестве параметра массив, p1, p2 и p3 — все экземпляры Promise, если нет, то сначала будет вызван метод Promise.resolve, указанный ниже, а параметры будут преобразованы в экземпляры Promise перед дальнейшей обработкой. . Состояние p в основном определяется полученным массивом обещаний (1) Только когда состояния p1, p2 и p3 будут выполнены, состояние p станет выполненным.В это время возвращаемые значения p1, p2 и p3 формируют массив и передают его обратному вызову функция с. (2) Пока один из p1, p2 и p3 отклонен, статус p становится отклоненным.В это время возвращаемое значение первого отклоненного экземпляра будет передано в функцию обратного вызова p.
Здесь стоит обратить внимание на то, есть ли ошибки в p1, p2, p3 и есть ли свои методы отлова. Он вызовет свой собственный catch, а когда метода catch нет, он будет передан в catch p для обработки.
2.5, гонка
Метод Promise.race также заключает несколько экземпляров Promise в новый экземпляр Promise. Его основной синтаксис:
const p = Promise.race([p1, p2, p3]);
В отличие от метода all: (1) Только когда состояния p1, p2 и p3 будут отвергнуты, p станет отвергнутым. (2) Если одно из состояний p1, p2 и p3 становится выполненным, состояние p станет выполненным и вернет возвращаемое значение выполнено в первый раз.
В-четвертых, первое рукописное обещание
Вероятно, будет перед так называемой обещанием использования, чтобы снова описать его, вероятно, написать один или два примера смогут почувствовать обещание тайны. Так что, если вы позволите себе написать обещание, вы должны быть как это написать?
Вот Обещание, которое я написал после работы:
const PENDING = "pending";
const RESOLVE = "resolve";
const REJECTED = "rejected";
function JPromise(fn){
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.state === PENDING){
that.state = RESOLVE;
that.value = value;
that.resolvedCallbacks.map(cb=>cb(that.value));
}
}
function reject(value){
if(that.state === PENDING){
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb=>cb(that.value))
}
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
}
JPromise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:v=>v;
onRejected = typeof onRejected === 'function'?onRejected:r=>new Error(r);
if(this.state === PENDING){
this.resolvedCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}
if(this.state ===RESOLVE){
onFulfilled(this.value);
}
if(this.state === REJECTED){
onRejected(this.value)
}
}
Затем запустите его:
new JPromise((resolve,rejected)=>{
resolve(1)
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
Затем я протестировал его сам, и он отлично может реализовать простое обещание. Но после того, как я пообщался с большим парнем, я понял, что у промиса есть свой набор стандартов.Хотя мой код может просто реализовать функцию промиса, он не соответствует этому набору стандартов.
5. Обещания, соответствующие спецификациям
Прежде всего, о спецификации промиса, кратко упомяну о ней:
- Каждый метод then возвращает новый объект Promise (ядро принципа)
- Если метод then явно возвращает объект Promise, этот объект имеет преимущественную силу, и будет возвращен его результат.
- Если метод then возвращает общее значение (такое как Number, String и т. д.), используйте это значение, чтобы обернуть его в новый объект Promise и вернуть его.
- Если в методе then нет оператора return, он считается возвращающим объект Promise, обернутый с помощью Undefined.
- Если в методе then есть исключение, вызовите метод состояния отказа (reject), чтобы перейти к onRejected следующего then.
- Если метод then не проходит ни в один обратный вызов, он продолжает передаваться вниз (функция передачи значения).
Наконец опубликуйте мой улучшенный код:
const PENDING = 'pending';//初始态
const FULFILLED = 'fulfilled';//初始态
const REJECTED = 'rejected';//初始态
function Promise(executor){
let self = this;//先缓存当前promise实例
self.status = PENDING;//设置状态
//定义存放成功的回调的数组
self.onResolvedCallbacks = [];
//定义存放失败回调的数组
self.onRejectedCallbacks = [];
//当调用此方法的时候,如果promise状态为pending,的话可以转成成功态,如果已经是成功态或者失败态了,则什么都不做
function resolve(value){ //2.1.1
if(value!=null &&value.then&&typeof value.then == 'function'){
return value.then(resolve,reject);
}
//如果是初始态,则转成成功态
//为什么要把它用setTimeout包起来
setTimeout(function(){
if(self.status == PENDING){
self.status = FULFILLED;
self.value = value;//成功后会得到一个值,这个值不能改
//调用所有成功的回调
self.onResolvedCallbacks.forEach(cb=>cb(self.value));
}
})
}
function reject(reason){ //2.1.2
setTimeout(function(){
//如果是初始态,则转成失败态
if(self.status == PENDING){
self.status = REJECTED;
self.value = reason;//失败的原因给了value
self.onRejectedCallbacks.forEach(cb=>cb(self.value));
}
});
}
try{
//因为此函数执行可能会异常,所以需要捕获,如果出错了,需要用错误 对象reject
executor(resolve,reject);
}catch(e){
//如果这函数执行失败了,则用失败的原因reject这个promise
reject(e);
};
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('循环引用'));
}
let called = false;//promise2是否已经resolve 或reject了
if(x instanceof Promise){
if(x.status == PENDING){
x.then(function(y){
resolvePromise(promise2,y,resolve,reject);
},reject);
}else{
x.then(resolve,reject);
}
//x是一个thenable对象或函数,只要有then方法的对象,
}else if(x!= null &&((typeof x=='object')||(typeof x == 'function'))){
//当我们的promise和别的promise进行交互,编写这段代码的时候尽量的考虑兼容性,允许别人瞎写
try{
let then = x.then;
if(typeof then == 'function'){
//有些promise会同时执行成功和失败的回调
then.call(x,function(y){
//如果promise2已经成功或失败了,则不会再处理了
if(called)return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},function(err){
if(called)return;
called = true;
reject(err);
});
}else{
//到此的话x不是一个thenable对象,那直接把它当成值resolve promise2就可以了
resolve(x);
}
}catch(e){
if(called)return;
called = true;
reject(e);
}
}else{
//如果X是一个普通 的值,则用x的值去resolve promise2
resolve(x);
}
}
//onFulfilled 是用来接收promise成功的值或者失败的原因
Promise.prototype.then = function(onFulfilled,onRejected){
//如果成功和失败的回调没有传,则表示这个then没有任何逻辑,只会把值往后抛
//2.2.1
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function(value){return value};
onRejected = typeof onRejected == 'function'?onRejected:reason=>{throw reason};
//如果当前promise状态已经是成功态了,onFulfilled直接取值
let self = this;
let promise2;
if(self.status == FULFILLED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onFulfilled(self.value);
//如果获取到了返回值x,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
//如果执行成功的回调过程中出错了,用错误原因把promise2 reject
reject(e);
}
})
});
}
if(self.status == REJECTED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
}
if(self.status == PENDING){
return promise2 = new Promise(function(resolve,reject){
self.onResolvedCallbacks.push(function(){
try{
let x =onFulfilled(self.value);
//如果获取到了返回值x,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
}
Как насчет конкретных деталей кода? Я отметил это в коде. Если вы не понимаете, вы можете общаться в частном чате или в WeChat. Добро пожаловать, чтобы беспокоить.
сказать в конце
Давненько я не вздремнул в полдень, и эта статья немного небрежна. Изначально это было 8:30, когда я вышел с работы, а потом долго разбирал/отлаживал код, в итоге долго не мог придумать пример, когда писал статью. Я воспользуюсь своим временем отдыха, чтобы пересмотреть эту статью завтра. Я слишком сонный. Давай сначала ляжем спать. Я принимаю душ и ложусь спать. Если вас не устраивает текущая статья, прочитайте ее еще раз через день.
Наконец, не могли бы вы дать мне немного больше популярности?Спустя более года написания, это все еще так популярно..