Большая часть содержания этой статьи переведена сэта статья
1. Что такое обещание
Обещания можно рассматривать как спецификацию кода для решения асинхронной обработки. Обычная асинхронная обработка заключается в использовании функций обратного вызова.Существует два режима функций обратного вызова: синхронный обратный вызов и асинхронный обратный вызов. Общая функция обратного вызова относится к асинхронному обратному вызову.
Синхронный обратный вызов
function add(a, b, callback) { callback(a + b) }
console.log('before');
add(1, 2, result => console.log('Result: ' + result);
console.log('after');
Результат: до Результат: 3 после
Асинхронный обратный вызов
function addAsync(a, b, callback) {
setTimeout( () => callback(a + b), 1000);
}
console.log('before');
addAsync(1, 2, result => console.log('Result: ' + result));
console.log('after');
Выходной результат: до после Результат: 3
Однако у функции обратного вызова есть известная яма, называемая «ад обратных вызовов», например:
doSomething1(function(value1) {
doSomething2(function(value2) {
doSomething3(function(value3) {
console.log("done! The values are: " + [value1, value2, value3].join(','));
})
})
})
Чтобы дождаться готовности данных value1, value2 и value3, необходимо слой за слоем вкладывать функции обратного вызова. Если он продолжит быть вложенным, он сформирует ад обратного вызова, который не способствует чтению кода.
Если вместо этого вы используете Promise, просто напишите его следующим образом.
doSomething1().then(function() {
return value1;
}).then(function(tempValue1) {
return [tempValue1, value2].join(',');
}).then(function(tempValue2) {
console.log("done! ", [tempValue2, value3].join(','));
});
Можно заметить, что Promise на самом деле берет функцию обратного вызова изdoSomething
Извлечено из функцииthen
метод, тем самым предотвращая проблему множественной вложенности.
ОдинPromiseОбъект представляет значение, которое в настоящее время недоступно, но может быть разрешено в какой-то момент в будущем. Он либо успешно анализирует, либо не выдает исключение. Это позволяет вам писать асинхронный код синхронным способом.
Обещания выполняются согласноPromises/A+спецификация реализована.
2. Объект и состояние обещания
Для основного использования и введения Promise вы можете обратиться кpromise-book. Вот более подробное введение в использование промисов.
2.1 resolve & reject
Конструктор Promise используется для создания объекта Promise, который передается в анонимную функцию.resolve
а такжеreject
Оба они также являются функциями. еслиresolve
Выполняется, запускает успешную функцию обратного вызова в promise.then; еслиreject
При выполнении срабатывает функция обратного вызова, отклоненная в promise.then.
var promise = new Promise(function(resolve, reject) {
// IF 如果符合预期条件,调用resolve
resolve('success');
// ELSE 如果不符合预期条件,调用reject
reject('failure')
})
2.2 Fulfilled & Rejected
Начальным значением объекта Promise является состояние готовности Pending.
казненresolve()
После этого значение состояния объекта Promise становится состоянием onFulfilled.
казненreject()
После этого значение состояния объекта Promise меняется на состояние onRejected.
Как только значение состояния объекта Promise определено (onFulfilled или onRejected), оно не изменится. То есть он не переключится с onFulfilled на onRejected или с onRejected на onFulfilled.
2.3 Ярлыки
Получите объект Promise в состоянии onFulfilled:
Promise.resolve(1);
// 等价于
new Promise((resolve) => resolve(1));
Получите объект Promise в состоянии onRejected:
Promise.reject(new Error("BOOM"))
// 等价于
new Promise((resolve, reject)
=> reject(new Error("BOOM")));
Дополнительные ярлыки см.Promise API.
3. Перехват исключения: потом и перехват
Есть два способа перехватывать исключения в Promise:
-
then
в анонимной функцииreject
метод -
catch
метод
3.1 Метод reject затем перехватывает исключения
Этот метод может захватывать исключение только в предыдущем объекте Promise, то есть вызыватьthen
Исключение, возникающее в объекте Promise функции.
var promise = Promise.resolve();
promise.then(function() {
throw new Error("BOOM!")
}).then(function (success) {
console.log(success);
}, function (error) {
// 捕捉的是第一个then返回的Promise对象的错误
console.log(error);
});
Но этот метод не может поймать исключение текущего объекта Promise, например:
var promise = Promise.resolve();
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}, function (error) {
console.log(error); // 无法捕捉当前then中抛出的异常
});
3.2 поймать поймать исключение
Если приведенный выше каштан переписать в следующем виде, а в конце добавить функцию перехвата, то исключение можно перехватить нормально.
var promise = Promise.resolve();
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).catch(function (error) {
console.log(error); // 可以正常捕捉到异常
});
catch
метод захватаthen
Ошибка, выданная в промисе, также может перехватывать ошибку, выданную предыдущим промисом. Поэтому рекомендуется пройтиcatch
метод для перехвата исключений.
var promise = Promise.reject("BOOM!");
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).catch(function (error) {
console.log(error); // BOOM!
});
Стоит отметить, что:catch
Фактически метод эквивалентенthen(null, reject)
, вышеизложенное можно записать как:
promise.then(function() {
return 'success';
}).then(function (success) {
console.log(success);
throw new Error("Another BOOM!");
}).then(null, function(error) {
console.log(error);
})
В итоге:
-
использовать
promise.then(onFulfilled, onRejected)
затем вonFulfilled
Если возникает исключение вonRejected
не может поймать это исключение. -
существует
promise.then(onFulfilled).catch(onRejected)
на случай, еслиthen
Исключения, сгенерированные в, могут быть.catch
пойман -
.then
а также.catch
По сути, нет никакой разницы и нужно использовать в разных случаях.
4. Практическое и пошаговое выполнение обещаний
Лучший способ что-то понять — попытаться реализовать это самостоятельно, хотя во многих местах это может быть неполным, очень полезно понять внутреннюю работу.
В основном цитируется здесьJavaScript Promises ... In Wicked DetailРеализация этой статьи, следующее содержание в основном перевод этой статьи.
4.1 Первоначальная реализация
Сначала реализуйте простой тип объекта Promise. Содержит только самые основныеthen
Методы иresolve
метод,reject
метод пока не рассматривается.
function Promise(fn) {
// 设置回调函数
var callback = null;
// 设置then方法
this.then = function (cb) {
callback = cb;
};
// 定义resolve方法
function resolve(value) {
// 这里强制resolve的执行在下一个Event Loop中执行
// 即在调用了then方法后设置完callback函数,不然callback为null
setTimeout(function () {
callback(value);
}, 1);
}
// 运行new Promise时传入的函数,入参是resolve
// 按照之前讲述的,传入的匿名函数有两个方法,resolve和reject
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
// 调用自己的Promise
doSomething().then(function (value) {
console.log("got a value", value);
});
Ну, это очень грубая версия Promise. В этой реализации даже не реализованы три состояния, которые требуются для Promise. Эта версия в основном визуально показывает основной метод Promise:then
а такжеresolve
.
Если эта версияthen
Асинхронный вызов по-прежнему приведет к тому, что обратный вызов в Promise будет нулевым.
var promise = doSomething();
setTimeout(function() {
promise.then(function(value) {
console.log("got a value", value);
})}, 1);
Последующее обслуживание промисов путем добавления состояния может решить эту проблему.
4.2 Обещание добавить состояние
добавив полеstate
Используется для поддержания состояния Promise при выполненииresolve
После функции изменитеstate
дляresolved
,исходныйstate
даpendding
.
function Promise(fn) {
var state = 'pending'; // 维护Promise实例的状态
var value;
var deferred; // 在状态还处于pending时用于保存回调函数的引用
function resolve(newValue) {
value = newValue;
state = 'resolved';
if (deferred) {
// deferred 有值表明回调已经设置了,调用handle方法处理回调函数
handle(deferred);
}
}
// handle方法通过判断state选择如何执行回调函数
function handle(onResolved) {
// 如果还处于pending状态,则先保存then传入的回调函数
if (state === 'pending') {
deferred = onResolved;
return;
}
onResolved(value);
}
this.then = function (onResolved) {
// 对then传入的回调函数,调用handle去执行回调函数
handle(onResolved);
};
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
doSomething().then(function (value) {
console.log("got a value", value);
});
После добавления состояния вы можете решить проблему последовательности вызовов, оценив состояние:
-
существует
resolve()
вызов перед выполнениемthen()
. Указывает, что значение не было обработано в это время, и состояние в это времяpending
, затем держитеthen()
Входящая функция обратного вызова и т. д., которые нужно вызватьresolve()
После обработки значения значения выполните функцию обратного вызова. В это время функция обратного вызова сохраняется вdeferred
середина. -
существует
resolve()
вызов после выполненияthen()
. Указывает, что значение прошлоresolve()
Обработка завершена. при звонкеthen()
Когда значение можно обработать, вызвав входящую функцию обратного вызова.
Эту версию Promise мы можем вызвать первойresolve()
илиpending()
, порядок двух не повлияет на выполнение программы.
4.3 Обещание добавляет цепочку вызовов
Обещания можно связать, каждый звонокthen()
После возврата нового экземпляра промиса необходимо модифицировать ранее реализованныйthen()
метод.
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
value = newValue;
state = 'resolved';
if (deferred) {
handle(deferred);
}
}
// 此时传入的参数是一个对象
function handle(handler) {
if (state === 'pending') {
deferred = handler;
return;
}
// 如果then没有传入回调函数
// 则直接执行resolve解析value值
if (!handler.onResolved) {
handler.resolve(value);
return;
}
// 获取前一个then回调函数中的解析值
var ret = handler.onResolved(value);
handler.resolve(ret);
}
// 返回一个新的Promise实例
// 该实例匿名函数中执行handle方法,该方法传入一个对象
// 包含了传入的回调函数和resolve方法的引用
this.then = function (onResolved) {
return new Promise(function (resolve) {
handle({
onResolved: onResolved, // 引用上一个Promise实例then传入的回调
resolve: resolve
});
});
};
fn(resolve);
}
function doSomething() {
return new Promise(function (resolve) {
var value = 42;
resolve(value);
});
}
// 第一个then的返回值作为第二个then匿名函数的入参
doSomething().then(function (firstResult) {
console.log("first result", firstResult);
return 88;
}).then(function (secondResult) {
console.log("second result", secondResult);
});
then
Также необязательно передавать функцию обратного вызова, например:
doSomething().then().then(function(result) {
console.log('got a result', result);
});
существуетhandle()
В реализации метода, если функция обратного вызова отсутствует, существующее значение анализируется напрямую, и это значение вызывается в предыдущем экземпляре Promise.resolve(value)
входящий в.
if(!handler.onResolved) {
handler.resolve(value);
return;
}
Что, если функция обратного вызова возвращает объект Promise вместо конкретного значения? На этом этапе нам нужно вызвать возвращенный Promisethen()
метод.
doSomething().then(function(result) {
// doSomethingElse returns a promise
return doSomethingElse(result);
}).then(function(anotherPromise) {
anotherPromise.then(function(finalResult) {
console.log("the final result is", finalResult);
});
});
Каждый раз писать это громоздко, мы можем сделать это в нашем промисеresole()
Это обрабатывается внутри метода.
function resolve(newValue) {
// 通过判断是否有then方法判断其是否是Promise对象
if (newValue && typeof newValue.then === 'function') {
// 递归执行resolve方法直至解析出值出来,
// 通过handler.onResolved(value)解析出值,这里handler.onResolve就是resolve方法
newValue.then(resolve);
return;
}
state = 'resolved';
value = newValue;
if (deferred) {
handle(deferred);
}
}
4.4 Обещание добавляет обработку отказа
Пока есть приличный промис, теперь добавим тот, который был проигнорирован в началеreject()
метод, который позволяет нам использовать промисы таким образом.
doSomething().then(function(value) {
console.log('Success!', value);
}, function(error) {
console.log('Uh oh', error);
});
Реализация тоже очень проста,reject()
метод сresolve()
Метод аналогичен.
function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;
function resolve(newValue) {
if (newValue && typeof newValue.then === 'function') {
newValue.then(resolve, reject);
return;
}
state = 'resolved';
value = newValue;
if (deferred) {
handle(deferred);
}
}
// 添加的reject方法,这里将Promise实例的状态设为rejected
function reject(reason) {
state = 'rejected';
value = reason;
if (deferred) {
handle(deferred);
}
}
function handle(handler) {
if (state === 'pending') {
deferred = handler;
return;
}
var handlerCallback;
// 添加state对于rejected状态的判断
if (state === 'resolved') {
handlerCallback = handler.onResolved;
} else {
handlerCallback = handler.onRejected;
}
if (!handlerCallback) {
if (state === 'resolved') {
handler.resolve(value);
} else {
handler.reject(value);
}
return;
}
var ret = handlerCallback(value);
handler.resolve(ret);
}
this.then = function (onResolved, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onResolved: onResolved,
onRejected: onRejected,
resolve: resolve,
reject: reject
});
});
};
fn(resolve, reject);
}
function doSomething() {
return new Promise(function (resolve, reject) {
var reason = "uh oh, something bad happened";
reject(reason);
});
}
// 调用栗子
doSomething().then(function (firstResult) {
// wont get in here
console.log("first result:", firstResult);
}, function (error) {
console.log("got an error:", error);
});
В настоящее время наш механизм обработки исключений может обрабатывать только информацию об исключении, созданную им самим, и не может нормально захватывать другую информацию об исключении, например, вresolve()
Исключение в методе. Мы модифицируем это следующим образом:
function resolve(newValue) {
try {
// ... as before
} catch(e) {
reject(e);
}
}
Здесь, добавивtry catch
Вручную ловите возможные исключения иcatch
вызыватьreject()
способ обработки. Точно так же для функций обратного вызова во время выполнения могут возникать исключения, и необходимо выполнить ту же обработку.
function handle(deferred) {
// ... as before
var ret;
try {
ret = handlerCallback(value);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(ret);
}
Для полного демо-кода выше, пожалуйста, обратитесь к оригинальному автору, предоставленномуfiddle.
4.4 Промисы гарантируют асинхронную обработку
На данный момент наш Promise реализовал базовые и относительно полные функции. Еще одна вещь, которую следует отметить здесь, это то, чтоСпецификация обещанияпредложить лиresolve()
ещеreject()
, выполнение должно оставаться асинхронным. Это легко сделать, просто внесите следующие изменения:
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
setTimeout(function() {
// ... as before
}, 1);
}
Вопрос, зачем так делать? В основном это делается для обеспечения согласованности и надежности потока выполнения кода. Рассмотрим следующие каштаны:
var promise = doAnOperation();
invokeSomething();
promise.then(wrapItAllUp);
invokeSomethingElse();
Намерение через код должно состоять в том, чтобы надеятьсяinvokeSomething()
а такжеinvokeSomethingElse()
После того, как все выполнено, выполните функцию обратного вызоваwrapItAllUp()
. Если обещаниеresolve()
Если обработка не асинхронная, порядок выполнения становитсяinvokeSomething()
-> wrapItAllUp()
-> invokeSomethingElse()
, что не соответствует ожидаемому.
Чтобы обеспечить согласованность этого порядка выполнения,Спецификация обещанияТребоватьresolve
Должен обрабатываться асинхронно.
На данный момент наше обещание в основном достойное. Конечно, есть еще отставание от реального промиса, например, отсутствие общеупотребительных удобных методов, таких какall()
,race()
Ждать. Однако метод, реализованный в этом примере, изначально основан на понимании принципа промиса, и я верю, что благодаря этому примеру мы получим более глубокое понимание принципа промиса.
Ссылаться на
- JavaScript Promises ... In Wicked Detail.
- Promises/A+.
- promise-book
- A quick guide to JavaScript Promises
- MDN web docs
- Node.js Design Patterns