Promise
— мой любимый синтаксис es6, о котором проще всего спросить на собеседовании. Так как же быть удобным в использовании и выделяться на собеседованиях?
Сначала зададим вопрос на собеседовании:
Вопрос из интервью: используйте Promise для инкапсуляции нативного ajax
Интервьюеры часто просят написать пакет Promise от руки, просто напишите следующую версию (если вы хотите узнать больше, вы можете расширить ее самостоятельно):
function ajaxMise(url, method, data, async, timeout) {
var xhr = new XMLHttpRequest()
return new Promise(function (resolve, reject) {
xhr.open(method, url, async);
xhr.timeout = options.timeout;
xhr.onloadend = function () {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
resolve(xhr);
else
reject({
errorType: 'status_error',
xhr: xhr
})
}
xhr.send(data);
//错误处理
xhr.onabort = function () {
reject(new Error({
errorType: 'abort_error',
xhr: xhr
}));
}
xhr.ontimeout = function () {
reject({
errorType: 'timeout_error',
xhr: xhr
});
}
xhr.onerror = function () {
reject({
errorType: 'onerror',
xhr: xhr
})
}
})
}
Введение в промисы
Promise
это объект, который содержит события, которые закончатся в будущем. У нее есть две характеристики, цитируемые из описания учителя Жуань Ифэн:
(1) Состояние объекта не зависит от внешнего мира. Объект Promise представляет собой асинхронную операцию с тремя состояниями: ожидание (выполняется), выполнено (успешно) и отклонено (сбой). Только результат асинхронной операции может определить текущее состояние, и никакая другая операция не может изменить это состояние. Это также происхождение названия Promise, что в переводе с английского означает «обещание», что означает, что другие средства не могут быть изменены.
(2) Как только состояние изменится, оно больше не изменится, и этот результат можно получить в любой момент. Есть только две возможности изменения состояния объекта Promise: с ожидания на выполнение и с ожидания на отклонение. Пока эти две ситуации имеют место, состояние затвердевает, оно больше не изменится, и всегда будет сохраняться результат, который называется разрешенным. Если изменение уже произошло, и вы добавляете функцию обратного вызова к объекту Promise, вы получите этот результат немедленно. Это совсем не то, что событие, характеристика события в том, что если вы пропустите его и послушаете снова, вы не получите результата.
Основное использование Promise
let promise1 = new Promise(function (resolve, reject){
setTimeout(function (){
resolve('ok') //将这个promise置为成功态(fulfilled),会触发成功的回调
},1000)
})
promise1.then(fucntion success(val) {
console.log(val) //一秒之后会打印'ok'
})
Самый простой код для реализации Promise
class PromiseM {
constructor (process) {
this.status = 'pending'
this.msg = ''
process(this.resolve.bind(this), this.reject.bind(this))
return this
}
resolve (val) {
this.status = 'fulfilled'
this.msg = val
}
reject (err) {
this.status = 'rejected'
this.msg = err
}
then (fufilled, reject) {
if(this.status === 'fulfilled') {
fufilled(this.msg)
}
if(this.status === 'rejected') {
reject(this.msg)
}
}
}
//测试代码
var mm=new PromiseM(function(resolve,reject){
resolve('123');
});
mm.then(function(success){
console.log(success);
},function(){
console.log('fail!');
});
Micro-task / event loop
упомянутый вышеPromise
Помимо отличия от событий, есть еще одно важное отличие, заключающееся в том, чтоPromise
Создатьmicro-task
. Посмотрите на вопросы лица:
Вопрос для интервью: Напишите порядок вывода следующего кода
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
});
console.log('script end');
Правильный ответ: «начало сценария», «конец сценария», «обещание1», «обещание2», «setTimeout». Причина в следующем:
- setTimeout (или событие) регистрируется как задача, управляемая циклом событий.
- Обещание регистрируется как микрозадача
Event Loop
Это важный механизм js, то есть он встречает события илиsetTimeout
После ожидания соответствующая callback-функция будет помещена в очередь событий (очередь задач), а при выполнении основной программы функции из очереди будут поочередно помещаться в стек для выполнения. Вы можете обратиться к Учителю Жуань Ифэну.Подробное объяснение механизма работы JavaScript: снова поговорим о цикле событий, но похоже, что сайт учителя подвергся атаке и не восстановился.
ноPromise
Вместо вышеописанного механизма она создает микрозадачу,micro-task
выполнение всегда заканчивается в текущем стеке выполнения и следующемtask
Перед выполнением последовательность такова: «текущий стек выполнения» -> «микрозадача» -> «обратный вызов из очереди задач» -> «микрозадача» -> ... (непрерывное потребление очереди задач) -> « микрозадача», короче говоря, когда текущий стек выполнения пуст,micro-task
пропускной пункт.
Нижеmicro-task
Определение:
Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task.
Promise
зарегистрированmicro-task
, поэтому в приведенном выше заголовке: сначала печатаются «начало сценария» и «конец сценария» в основном потоке, затем очищается очередь микрозадач, печатаются «обещание1», «обещание2», а затем вынимаетсяtask queue
Обратный вызов в setTimeout выполняется, setTimeout печатает.
Почему появляются обещания
Promise
Он предоставляет новое решение для асинхронного программирования js, потому что функция обратного вызова, которую мы использовали, на самом деле является большой проблемой, но она ограничена одним потоком js и должна быть написана в больших количествах. КонечноPromise
Не избавившись полностью от обратных вызовов, она просто изменила, где передавались обратные вызовы. Так что же не так с традиционными обратными вызовами?
вложенный
Упомянутая здесь вложенность означает, что большое количество функций обратного вызова затруднит чтение и изменение кода.Представьте себе сценарий: позвольте вам упомянуть следующий вызов url4 перед url2. Вам нужно очень аккуратно вырезать код и неуклюже вставлять, и вы не смеете изменять параметр result4, потому что это требует много дополнительной работы и сопряжено с риском.
$.ajax('url1',function success(result1){
$.ajax('url2',function success(result2){
$.ajax('url3',function success(result3){
$.ajax('url4',function success(result4){
//……
})
})
})
})
Конечно, вышеописанная проблема несколько драматична, и в реальности такая сложная ситуация возникает редко. По сравнению с этим умственное непонимание, вызванное функциями обратного вызова, более смертоносно, потому что наш мозг предпочитает синхронную логику, поэтомуawait
Причина, по которой ключевые слова так популярны.
Помню, когда я делился новыми возможностями JS со своими одноклассниками, я сказал:await
Ключевые слова, кто-то воскликнул: «Вау! Это неплохо, на этом можно писать код, как писать на java».
доверять
В дополнение к неэлегантному написанию и сложности сопровождения функция обратного вызова на самом деле имеет проблему доверия.
На самом деле функция обратного вызова может быть вызвана не так, как вы ожидаете. Потому что управление не в ваших руках. Эта проблема называется «инверсия управления». Например следующий пример:
$.ajax('xxxxxx',function success(result1){
//比如成功之后我会操作数据库记录结算金额
})
вышеjQuery
серединаajax
call, мы ожидаем, что третья сторона (jQ) выполнит мою программу (обратный вызов) для нас после завершения некоторого события.
Ну, между нами и третьей стороной нет контракта или спецификации, которой нужно следовать, если только вы не прочитаете стороннюю библиотеку, которую хотите использовать, и не убедитесь, что она делает то, что вы хотите, но на самом деле трудно быть уверенным. Даже в нашем собственном коде или в инструментах, которые мы пишем, трудно быть заслуживающим 100% доверия.
Обещайте решение
Promise
— это спецификация, которая пытается писать код более дружественным образом.Promise
Объект принимает функцию в качестве аргумента, а функция предоставляет два аргумента:
- решить: будет
promise
Переключиться из никогда не завершенного в успешное состояние, то есть из упомянутого вышеpending
переключиться наfufilled
,resolve
Параметры можно передавать, следующий уровеньpromise
Функция успеха в получит его - отвергнуть: будет
promise
перейти из никогда не завершенного в неудачное состояние, т. е. изpending
переключиться наrejected
let promise1 = new Promise(function(reslove, reject){
//reslove或者reject或者出错
})
promise1.then(fufilled, rejected).then().then() //这是伪代码
promise1.then(fufilled, rejected)//可以then多次
function fufilled(data) {
console.log(data)
}
function rejected(e){
console.log(e)
}
Как и две функции, упомянутые выше, как только состояние изменяется, этоPromise
завершил разрешение (больше не будет изменено) и возвращает новыйPromise
, который можно связать. и может зарегистрировать несколькоthen
метод, они решают одновременно и не влияют друг на друга. Этот дизайн значительно более элегантен, чем функции обратного вызова, и его легче понять и поддерживать. Так как же она улучшилась с точки зрения доверия?
Promise
Отношение «инверсия управления» «инвертируется» обратно через механизм уведомления. Обратный вызов — это функция, которую я передаю третьей стороне, ожидая, что она будет выполнена для меня, когда произойдет событие, иPromise
Разве что, согласно предпосылке всех, следующих спецификации, я буду уведомлен, когда произойдет событие, и тогда я решу что-то сделать (выполнить какую-то функцию). Видите ли, есть принципиальная разница.
Кроме того, функция обратного вызова имеет следующие проблемы с доверием:Promise
Они также имеют связанные ограничения:
- Обратный вызов вызван слишком рано
- Обратный вызов вызван слишком поздно (или не вызван)
- слишком много звонков
- Никакие аргументы не были успешно переданы вашему обратному вызову
- выплевывать ошибки или исключения
слишком рано или слишком поздно
ОдинPromise
Обратный вызов должен быть вызван по завершении текущего выполнения стека и следующей асинхронной временной точки, даже если он является синхронным, как показано ниже.resolve
Код также выполняется асинхронно, в то время как функция обратного вызова, которую вы передаете в библиотеку инструментов, может выполняться синхронно (вызывается слишком рано) или может быть забыта для выполнения (или слишком поздно).
new Promise(function (resolve) {
resolve(111111);
})
Слишком много раз или параметры не переданы
Promise
Можно принять решение только один раз, если вы примете решение несколько раз, она выполнит только первое решение, например:
new Promise(function (reslove, reject) {
resolve()
setTimeout(function () {
resolve(2)
},1000)
resolve(3)
}).then(function (val) {
console.log(val) //undefined
})
Передаются параметры успешного обратного вызоваresolve
Передается, например, как код выше, никакие параметры не передаются, тогда то, что получает val, будетundefined
, значит, параметры все равно получены. Уведомление:resolve
Принимается только один параметр, последующие параметры игнорируются.
глотать ошибки
Promise
Механизм обработки ошибок таков: если отображаемый вызовreject
И передайте причину ошибки, это сообщение будет передано обратному вызову отклонения.
Кроме того, если в каком-либо процессе возникает ошибка (например, TypeError или ReferenceError), ошибка будет обнаружена, иPromise
Отклонение, то есть сообщение об ошибке также будет передано обратному вызову отклонения, который отличается от традиционного обратного вызова.При возникновении ошибки традиционный обратный вызов вызовет синхронный ответ, но если ошибки нет, он будет быть асинхронным.
обещание контроля параллелизма
все / раса
all
а такжеrace
Обе функции выполняются одновременноpromise
методы, их возвращаемое значение такжеpromise
,all
буду ждать всехpromise
После того, как все решения приняты, иrace
Это то, что пока есть решение, оно будет разрешено.
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
Примечание. Если параметр пуст,
all
Метод разрешения немедленно, иrace
Метод будет зависать.
Вопрос из интервью: инкапсулировать метод promise.all
Promise.all = function(ary) {
let num = 0
let result = []
return new Promise(function(reslove, reject){
ary.forEach(promise => {
promise.then(function(val){
if(num >= ary.length){
reslove(result)
}else{
result.push(val)
num++
}
},function(e){
reject(e)
})
})
})
}
thenalbe
Как определить, что объект является обещанием?
Ты можешь подуматьinstanceof Promise
, но к сожалению нет. Причина в том, что каждая среда инкапсулирует свою собственнуюPromise
, вместо использования родногоES6 Promise
.
Итак, текущее решениеPromise
Один из способов – определить, является лиthenable
объект (если это объект или функция и он имеетthen
метод).
Это распространенный метод определения типа в js — определение типа утки:
Определение типа утки: если она выглядит как утка и крякает как утка, то это утка
resolve/reject
resolve
возвращает немедленный успехPromise
,reject
возвращает немедленную ошибкуPromise
,они естьnew Promise
синтаксический сахар, поэтому следующие два выражения эквивалентны:
let p1 = new Promise(function(resolve, reject){
reslove(11111)
})
let p2 = Promise.resolve(11111) //这和上面的写法结果一样
Кроме того, если входящиеreslove
параметр метода неpromise
ноthenable
значение, тоreslove
расширит его. Окончательное значение разрешения определяется выражениемthen
метод решить.
обработка ошибок
Как указано выше,Promise
заключается в асинхронной обработке ошибок, что означает, что моя ошибка будет в следующемPromise
можно отловить, что в большинстве случаев хорошо, но есть проблема: а что, если код, отлавливающий ошибку, появится снова?
Мой подход обычно заключается в добавлении в конец кодаcatch
:
let p1 = new Promise(function(reslove, reject){
ajax('xxxxx')
})
p1
.then(fullfilled, rejected)
.then(fullfilled, rejected)
.catch(function(e){
//处理错误
})
конец
На этом статья подходит к концу, если вы ее прочитали и что-то подумали, то я рад.
Буду продолжать обновлять дальшеPromise
+generator
, асинхронные функции и т. д.Promise
Соответствующие знания, желающие добиться прогресса вместе.