предисловие
Среда выполнения языка Javascript单线程
(один поток, что означает, что одновременно может выполняться только одна задача. Если задач несколько, они должны быть поставлены в очередь, предыдущая задача завершается, выполняется следующая задача и т. д.)
Преимущество этого режима в том, что его относительно просто реализовать, а среда выполнения относительно проста. вся программа, а обычные браузеры не отвечают (подвешенная смерть), часто из-за того, что определенный фрагмент кода Javascript выполняется в течение длительного времени (например, бесконечный цикл), в результате чего вся страница зависает на этом месте, и другие задачи не могут быть выполнены. казнен
Чтобы решить эту проблему, Javascript разделяет режим выполнения задач на два типа: синхронный и асинхронный.
同步模式
То есть последняя задача дожидается окончания предыдущей задачи, а затем выполняет ее Порядок выполнения программы согласуется и синхронизируется с порядком расположения задач.
异步模式
Это совсем другое.Каждая задача имеет одну или несколько функций обратного вызова.После завершения предыдущей задачи она не выполняет последнюю задачу, а выполняет функцию обратного вызова.Последняя задача выполняется не дожидаясь окончания предыдущей задачи.Поэтому , порядок выполнения программы и порядок задач непоследовательны и асинхронны. На стороне браузера операции, которые занимают много времени, должны выполняться асинхронно, чтобы избежать потери ответа браузером. Лучший пример — операции Ajax. На стороне сервера异步模式
Даже единственный режим, т.к. среда выполнения однопоточная, если разрешить синхронное выполнение всех http-запросов, то производительность сервера резко упадет
Все вышеперечисленное — бесполезные слова, потому что всем это очень ясно, тогда вопрос в том, знаете ли вы несколько асинхронных решений?
В этой статье будут описаны следующие известные асинхронные решения и их отличия от мелких до глубоких.
- функция обратного вызова
- Парсинг прослушивания событий (pub/sub)
- Анализ обещаний и опыт исходного кода от 0 до 1
- Комплексный анализ генератора
- Асинхронный/ожидающий анализ
Я не успела внести свой вклад в страну к Празднику Весны.Я написала этот пост.Эта статья немного длинная,потому что мне пришлось написать четыре статьи,чтобы описать их по отдельности,но думаю,так проще сравнивать и сравнивать понять их целиком. Если вы готовы потратить 20 минут на чтение этой статьи, вы обязательно что-то получите. Я пишу терпеливо и надеюсь, что вы сможете терпеливо читать ее. Студенты с плохой базой могут читать ее блоками. Заголовки подробно описаны все уровни, я надеюсь пройти эту статью, она может позволить каждому глубже понять большое асинхронное программирование JS
Да кстати ставьте лайк и читайте сначала, и формируйте привычку.Ведь кодировать слова не просто.Каждый лайк и комментарий от каждого будет давать мне некую мотивацию для написания следующей статьи.Спасибо 😁
функция обратного вызова
Кратко опишите функцию обратного вызова
Callback-функция должна быть понятна всем, простое понимание состоит в том, что функция передается в качестве параметра другой функции.
Обратный вызов не обязательно должен быть асинхронным, прямой зависимости нет, но функция обратного вызова — это решение для асинхронного
Кратко поясним на примере
function fn1(callback){
console.log("1")
callback && callback()
}
function fn2(){
console.log("2")
}
fn1(fn2)
Как показано в приведенном выше коде, параметр функции fn1 является обратным вызовом. При вызове fn1 передается функция fn2. Затем, когда функция fn1 выполняется для вызова функции обратного вызова, она вызывает fn2 для выполнения. Это типичная функция обратного вызова. , но это синхронно Мы можем использовать это для решения асинхронности следующим образом
fn1(callback){
setTimeout(() => {
callback && callback()
}, 1000)
}
fn1(()=>{
console.log("1")
})
Как показано выше, мы используем setTimeout для имитации задачи, которая занимает 1 с в функции fn1.Когда трудоемкая задача завершится, будет выбран обратный вызов, поэтому мы можем выполнить обратный вызов после завершения трудоемкой задачи функции fn1. , функция
Таким образом, мы превращаем синхронную операцию в асинхронную, и fn1 не будет блокировать выполнение программы, что эквивалентно выполнению основной логики программы в первую очередь и задержке выполнения трудоемких операций.
Преимущества/недостатки callback-функций
преимущество
Одним словом, функция обратного вызова — это самый простой метод асинхронного программирования, и ее преимущества заключаются в простоте, простоте понимания и развертывания.
недостаток
Самый большой недостаток функции обратного вызова заключается в том, что она не способствует чтению и сопровождению кода, а различные части сильно связаны (Coupling).
fun1(() => {
fun2(() => {
fun3(() => {
fun4(() => {
fun5(() => {
fun6()
})
})
})
})
})
Приведенный выше код очень распространен при использовании запросов AJAX ранее, потому что слишком много бизнес-требований для инициирования другого запроса после завершения одного запроса, код не элегантен, его нелегко читать и поддерживать, высокая связанность и уровни вложенности. вызвать этоад обратного звонка
В асинхронном обратном вызове стек выполнения функции обратного вызова отделен от исходной функции, исключение не может быть перехвачено извне, и исключение станет неуправляемым.
Хотя есть много недостатков, функция обратного вызова также незаменима в ежедневной разработке функции обратного вызова, просто будьте внимательны при ее использовании.
Функция обратного вызова относительно проста и широко используется, поэтому сначала я расскажу о ней, а затем мы рассмотрим мониторинг событий.
Мониторинг событий (режим публикации и подписки)
Для решения асинхронности можно использовать event-driven, выполнение задач зависит не от порядка кода, а от того, происходит ли событие
Опубликовано Ruan Yifeng ранее4 метода асинхронного программирования Javascript (см. ссылку [1])В этой статье мониторинг событий и публикация-подписка используются как два разных решения, но я лично считаю, что эти два решения можно объединить в одно, оба из которых управляются событиями с использованием модели публикации-подписки, поэтому я объясню это в один кусочек.
Jquery реализация слушателей событий
Реализация jquery относительно проста, потому что jq инкапсулирует для нас метод, и мы можем его использовать, но JQ обычно не используется, поэтому давайте кратко разберемся.
Мы можем использовать jqueryon
для прослушивания событий используйтеtrigger
Триггерные события, как показано ниже
$("body").on("done", fn2)
function fn1() {
setTimeout(function() {
$("body").trigger("done")
}, 2000)
}
function fn2() {
console.log("fn2执行了")
}
fn1()
мы используем JQon
Прослушивание пользовательского событияdone
, передается обратный вызов fn2, указывающий, что функция fn2 выполняется сразу после запуска события
Трудоемкая задача моделируется с помощью setTimeout в функции fn1, которая используется в обратном вызове setTimeout.trigger
запущенныйdone
мероприятие
мы можем использоватьon
Чтобы связать несколько событий, для каждого события можно указать несколько функций обратного вызова.
JavaScript реализует мониторинг событий
В JS мы должны сами реализовать что-то похожее на JQ.on
а такжеtrigger
охватывать
В процессе реализации используется шаблон проектирования, то есть шаблон публикации-подписки, поэтому кратко упомянем
Кратко опишите режим публикации-подписки (режим наблюдателя)
Шаблон публикации-подписки, также известный как шаблон наблюдателя, определяет зависимость между объектами "один ко многим". Когда состояние объекта изменяется, все объекты, которые зависят от него, будут уведомлены.
Давайте посмотрим на более разочаровывающий пример
小李辛辛苦苦做了两年程序猿,攒了些钱,内心激动,要去售楼部买一个心仪已久的房型
到售楼部问了下,售楼部说暂时没有这种房型的房源了,怎么办呢,下次再来吧
但是小李不知道这种房型什么时候有房源,总不能每天打电话到售楼部问吧,小李就把电话和房型信息留到售楼部了,什么时候有这种房源了,售楼部会短信通知
要知道,售楼部不会只通知小李一个人,售楼部会把预留信息所有房型信息一致的人都通知一遍
在这个比较挫的例子中,小李包括每个买房的人都是订阅者,而售楼部就是发布者
На самом деле мы все использовали модель публикации-подписки, например, когда мы привязываем функцию события к узлу DOM, мы уже использовали ее.
document.body.addEventListener('click', function () {
console.log(1)
})
Но это только самое простое использование модели публикации-подписки.Во многих сценариях мы часто реализуем некоторые пользовательские события для удовлетворения наших потребностей.
Например, если мы хотим написать собственный прослушиватель событий в соответствии с JQ, нам нужно прослушивать событие и выполнять его обратный вызов прослушивателя при запуске события.
Реализовать прослушиватели событий в режиме публикации-подписки.
Существует множество способов реализации модели публикации-подписки, ниже мы используемclass
сделать это простым
class Emitter {
constructor() {
// _listener数组,key为自定义事件名,value为执行回调数组-因为可能有多个
this._listener = []
}
// 订阅 监听事件
on(type, fn) {
// 判断_listener数组中是否存在该事件命
// 存在将回调push到事件名对应的value数组中,不存在直接新增
this._listener[type]
? this._listener[type].push(fn)
: (this._listener[type] = [fn])
}
// 发布 触发事件
trigger(type, ...rest) {
// 判断该触发事件是否存在
if (!this._listener[type]) return
// 遍历执行该事件回调数组并传递参数
this._listener[type].forEach(callback => callback(...rest))
}
}
Как показано выше, мы создалиEmitter
добавлен класс с двумя методами-прототипамиon
а такжеtrigger
, В приведенном выше коде есть комментарии, поэтому я не буду слишком много объяснять. Учащиеся с плохой базой читают его несколько раз и набирают самостоятельно. Это относительно просто.
при его использовании
// 创建一个emitter实例
const emitter = new Emitter()
emitter.on("done", function(arg1, arg2) {
console.log(arg1, arg2)
})
emitter.on("done", function(arg1, arg2) {
console.log(arg2, arg1)
})
function fn1() {
console.log('我是主程序')
setTimeout(() => {
emitter.trigger("done", "异步参数一", "异步参数二")
}, 1000)
}
fn1()
Как показано выше, мы сначала создаем экземпляр эмиттера, затем регистрируем событие, а затем инициируем событие.Использование такое же, как и в приведенном выше JQ, который решает асинхронную проблему.
Реализация Vue представляет собой относительно сложный режим публикации и подписки.trigger
Измените имя наemit
Привычнее?Конечно у нас относительно просто.Ведь код всего шесть-семь строчек,но принцип такой принцип
Преимущества/недостатки мониторинга событий
преимущество
Для мониторинга событий, реализованного в режиме публикации-подписки, мы можем привязывать несколько событий, и каждое событие также может указывать несколько функций обратного вызова, что больше соответствует идее модульности.Когда мы пишем свои собственные слушатели, мы можем сделать много оптимизации для лучшего мониторинга.Программа работает
недостаток
Вся программа стала событийно-управляемой, что в большей или меньшей степени повлияет на процесс.Трудно регистрировать прослушиватели событий и запускать их каждый раз, когда они используются.Код не очень элегантный.Не то, чтобы событийно-управляемая была плоха .Решить асинхронную проблемуВот и все, есть решение получше
Promise
Коротко об обещаниях
ES2015 (ES6) стандартизировал и представил объект Promise, решение для асинхронного программирования.
Проще говоря, это написание асинхронного кода синхронным способом, который можно использовать для решения проблемы обратного вызова.
Особенности обещания
Особенность 1
Обещание, выполнение обещания, состояние объекта Promise не зависит от внешнего мира
Объект Promise представляет собой асинхронную операцию и имеет три состояния.
-
В ожидании
-
Завершено (Решено/Выполнено)
-
Не удалось (отклонено)
Только результат асинхронной операции может определить, в каком состоянии он находится в данный момент, и никакая другая операция не может изменить это состояние.
Отсюда и произошло название Promise, что в переводе с английского означает承诺
, указывая, что другие средства не могут измениться
Особенность 2
Как только состояние объекта Promise изменится, оно больше не изменится.
Состояние объекта Promise меняется, есть только две возможности
-
От ожидания до решения
-
От ожидания к отклонению
Пока происходят эти две ситуации, состояние застынет и не изменится, и всегда будет поддерживать этот результат.
Обещания использования
Обещание — это конструктор, мы можем передатьnew
ключевое слово для создания экземпляра Promise, или вы можете напрямую использовать некоторые статические методы Promise
новый экземпляр обещания
грамматика
new Promise( function(resolve, reject) {...});
Пример
function fn1(){
return new Promise((resolve,reject) => {
setTimeout(()=>{
let num = Math.ceil(Math.random()*10)
if(num < 5){
resolve(num)
}else{
reject('数字太大')
}
},2000)
})
}
Как показано выше, мы используемnew
ключевое слово создает экземпляр обещания, а в функции fn1return
вышел из
new Promise
Создается экземпляр промиса, и конструктор промиса принимает в качестве параметра функцию, называемую функцией-исполнителем.
Функция-обработчик получает два параметра, которыеresolve
а такжеreject
, эти два параметра также являются двумя функциями обратного вызова
resolve
Функция вызывается при успешном выполнении асинхронной операции, а результат асинхронной операции передается в качестве параметра
reject
Функция вызывается при сбое асинхронной операции, и в качестве параметра передается сообщение об ошибке асинхронной операции.
Простое понимание состоит в том, что один из них является успешным обратным вызовом, а другой - неудачным обратным вызовом.
Promise.prototype.then()
Объекты Promise имеют метод-прототипthen
После создания экземпляра Promise вы можете использоватьthen
Спецификация методаresolved
статус иreject
функция обратного вызова состояния
Promise.prototype.then(onFulfilled[, onRejected])
then
Метод получает два обратных вызова onFulfilled и onRejected.
-
onFulfilled - необязательно
- Функция обратного вызова, вызываемая, когда обещание выполняется
- Функция имеет один параметр, значение выполнения
- Если параметр не является функцией, он внутренне заменяется на
(x) => x
, то есть функция, которая возвращает окончательный результат промиса как есть
-
onRejected - необязательно
- Функция обратного вызова, вызываемая, когда обещание принимается или отклоняется
- Функция имеет один параметр, причина отклонения
- Если параметр не является функцией, он внутренне заменяется на
Thrower
Функция (выдает ошибку, полученную в качестве аргумента)
then
После того, как метод получит экземпляр промиса, он вернет новый экземпляр промиса (не исходный экземпляр промиса), а возвращаемое значение исходного экземпляра промиса будет передано в новый экземпляр промиса в качестве параметра.resolve
функция
то с тех порthen
метод возвращает новый экземпляр обещания, поэтому мы можем использоватьthen
методы, т. е. цепочки, также известные как операции **композиции**
Следуя приведенному выше примеру, функция fn1 вернет экземпляр обещания.
fn1()
.then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
Как показано выше, мы использовалиthen
Два параметра метода
Обратный вызов первого параметра встречается очень часто, фактически Promise переходит в завершенное состояние и получает переданное значение.
Обратный вызов второго параметра заключается в том, что Promise принимает или отклоняет и получает неправильный параметр.Мы можем использовать меньше, обычно мы используемcatch
метод,then
Второй параметр метода onRejected иcatch
Есть еще некоторые тонкие различия, которые будут упомянуты ниже.
Согласно обещаниям/A+then
Определение метода, давайте посмотримthen
Особенности метода
первыйthen
метод должен возвращатьpromise
объект (акцент)
Принцип цепных вызовов, независимо от того, какой метод then вернет новый объект Promise, так что будет следующий метод then
еслиthen
Если метод возвращает общее значение (такое как Number, String и т. д.), используйте это значение, чтобы обернуть его в новый объект Promise и вернуть его.
Как в примере ниже,then
Метод получает объект Promise,then
Когда в методе возвращается нормальное значение, следующийthen
можно получить в
let p =new Promise((resolve,reject)=>{
resolve(1)
})
p.then(data=>{
return 2 // 返回了一个普通值
}).then(data=>{
console.log(data) // 2
})
еслиthen
В методе нет способаreturn
Заявление, он возвращает объект обещания, завернутый с неопределенным
Вывод следующего примера
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(data => {
// 无return语句
}).then(data => {
console.log(data) // undefined
})
еслиthen
Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.then
onRejected
then
Второй параметр метода onRejected заключается в том, что ток нельзя контролироватьthen
Если обратный вызов метода ненормальный, спецификация определяет текущийthen
Если в методе есть исключение, вызывается метод в состоянии failed (reject) и поток переходит к следующемуthen
onRejected
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(data => 2)
.then(
data => {
throw "this is err"
},
err => {
console.log("err1:" + err)
}
)
.then(
data => {
console.log(data)
},
err => {
console.log("err2:" + err) // err2:this is err
}
)
еслиthen
Если метод не проходит ни в один обратный вызов, он продолжает проходить вниз (так называемое проникновение значения)
В следующем примере в первомthen
Метод вызывается после двух пустых последовательных вызововthen
метод, функция обратного вызова не передается, и возвращаемое значение не передается.В это время Promise будет передавать значение вниз, пока оно не будет получено и обработано, что является так называемым проникновением значения
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(data => 2)
.then()
.then()
.then(data => {
console.log(data) // 2
})
еслиthen
Метод возвращает объекты Promise, подчиняется объекту и возвращает его результаты.
Без лишних слов, давайте посмотрим на пример
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(data => {
return new Promise((resolve, reject) => {
resolve(2)
})
}).then(data => {
console.log(data) // 2
})
Promise.prototype.catch()
За исключением метода-прототипаthen
Кроме того, объект Promise имеетcatch
Метод-прототип
catch
Метод может использоваться для обработки ошибок в составе промиса, этот метод возвращает промис и обрабатывает отклонение
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
- onRejected
- Когда обещание отклонено, вызывается функция обратного вызова, функция имеет параметр для причины сбоя или сообщения об ошибке.
Простое понимание состоит в том, чтобы перехватывать исключения.Если ошибка выдается в комбинации промисов или отклоняется в комбинации промисов, она будет перехвачена.
Также следуйте верхнему примеру, но также используйте функцию fn1
fn1()
.then(data=>{
console.log(data)
}).catch(err=>{
console.log(err)
})
Лучше ли отлавливать ошибки или сбои таким образом, чемthen
Второй параметр метода выглядит более удобным, ведь Promise привязывается до конца.
Также обратите внимание, чтоcatch
Метод также возвращает новый экземпляр обещания, еслиonRejected
Обратный вызов выдает ошибку или возвращает обещание, которое само по себе не выполняется, черезcatch
Возвращенный промис будет отклонен, в противном случае это успешный (разрешенный) экземпляр промиса.
и вышеthen
Второй параметр в методе почти такой же, рассмотрим на примере
fn1()
.catch(err => {
console.log(err)
return err
})
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})
Вышеприведенная функция fn1 имеет половинную вероятность возврата отклоненного кода при возврате отклоненного следующегоthen
Обратный вызов метода также будет выведен, потому что мы находимся в первомcatch
Возвращается только сообщение об ошибке, и не выдается ни ошибки, ни невыполненного обещания, поэтому первыйcatch
Выполнение объекта обещания возврата разрешается
Promise.prototype.finally()
наконец, английский язык最后
значит этот методES2018
стандарт
Метод прототипаfinally
, мы можем не использовать много, синтаксис следующий
p.finally(onFinally);
p.finally(function() {
// 返回状态为(resolved 或 rejected)
});
Объясни одним предложениемfinally
, в конце обещания оно будет выполнено независимо от успеха или неудачиonFinally
Обратный вызов, обратный вызов не имеет параметров
Применение того же утверждения требует вthen()
а такжеcatch()
Напишите один раз в каждом случае
Promise.resolve()
Суммируйте метод Promise.resolve() в одном предложении, получите значение и преобразуйте существующий объект в объект Promise.
Promise.resolve(value)
Как показано ниже, значение может быть любого типа или может быть объектом Promise.
const p = Promise.resolve(123);
Promise.resolve(p).then((value)=>{
console.log(value); // 123
});
Promise.reject()
Promise.reject()
Метод тот же, что и вышеPromise.resolve()
то же самое, но возвращаетPromise
объект
Promise.reject(123)
.then(data => {
console.log(data)
})
.catch(err => {
console.log("err:" + err)
})
// err:123
Promise.all()
Promise.all(iterable)
Используется для переноса нескольких экземпляров Promise в новый экземпляр Promise, параметр представляет собой массив экземпляров Promise.
Итерируемый тип был введен стандартом ES6 и представляет собой итерируемый объект.Array
,Map
а такжеSet
принадлежатьiterable
Type , об iterable мы поговорим ниже, здесь мы можем сначала понять этот параметр как массив, а потом уже кооперироваться со следующим iterable для понимания
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
let p = Promise.all([p1,p2,p3]);
p.then(data=>{
console.log(data) // [1,2,3]
})
Как показано выше, когда все состояния p1, p2 и p3 являются Resolved, состояние p будет Resolved.
Пока есть экземпляр Rejected , возвращаемое значение первого экземпляра Rejected будет передано в функцию обратного вызова P
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
let p3 = Promise.reject(3)
let p = Promise.all([p1, p2, p3])
p.then(data => {
console.log(data)
}).catch(err => {
console.log("err:" + err) // 3
})
Сценарий приложения будет больше, когда у нас есть один интерфейс и нам нужны данные, возвращаемые двумя или более другими интерфейсами в качестве параметров.
Promise.race()
Promise.race(iterable)
и вышеPromise.all(iterable)
аналогичный
all
Метод должен выполняться, когда все состояния в итеративном объекте изменяются.
race
Метод прямо противоположный, пока изменяется одно состояние итеративного объекта, изменяется его состояние, а возвращаемое значение экземпляра измененного состояния передается в функцию обратного вызова.
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "1")
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "2")
})
Promise.race([p1, p2])
.then(value => {
console.log(value) // 2
})
Promise.try()
В разработке я часто сталкиваюсь с ситуацией: я не знаю или не хочу различать, является ли функция fn синхронной функцией или асинхронной функцией, но я хочу использовать Promise для ее обработки
Потому что таким образом, независимо от того, является ли fn асинхронным или нет, метод then может использоваться для указания следующего процесса, а метод catch может использоваться для обработки ошибок, выдаваемых fn.
мы можем использоватьPromise.resolve
преобразовать его в объект Promise
let fn = () => console.log("fn")
Promise.resolve(fn).then(cb => cb())
console.log("hahaha")
// hahaha
// fn
Но с этим есть проблема.Если функция fn синхронная, то эта волна операций преобразует ее в асинхронную, как вывод выше
Так есть ли способ сделать синхронные функции, выполняющие синхронно и асинхронные функции асинхронно, и пусть они имеют единую API? конечно может
мы можем это сделать
const fn = () => console.log('fn');
(
() => new Promise(
resolve => resolve(fn())
)
)().then(()=>{
console.log(222)
})
.catch(err=>{
console.log(err)
})
console.log('111');
// fn
// 111
// 222
так же может быть
const fn = () => console.log("fn");
(async () => fn())()
.then(() => {
console.log(222)
})
.catch(err => {
console.log(err)
})
console.log("111")
// fn
// 111
// 222
Однако код немного странный и неэлегантный.
см. использованиеtry
метод
const fn = () => console.log("fn");
Promise.try(fn)
.then(() => {
console.log(222)
})
.catch(err => {
console.log(err)
})
console.log("111")
// fn
// 111
// 222
Как показано выше, он лаконичный и ясный, и при этом очень практичный.
фактически,Promise.try
это имитировать блок попробовать, какpromise.catch
Моделирование - это блок кода catch
наконецPromise.try
Не является частью Javascript
Это предложение было еще в 2016 году, его могут понять заинтересованные студенты, сейчас оно не записано и не включено в стандарт.
Если вы хотите использовать его, вам нужно использовать библиотеку Promise Bluebird, Q и т. д. или ввести Polyfill
Хотя он не был включен в стандарт, это не означает, что им непросто пользоваться, и каждый может испытать его на себе.
Если вы хотите узнать больше об этом методе, мы рекомендуем вам просмотреть справочную ссылку [4] [5]
Разница между onRejected и catch
упомянутый вышеpromise.then(onFulfilled, onRejected)
Второй параметр в onRejected иcatch
Увидев это, вы можете спросить, а в чем разница между одними и теми же исключениями, которые перехватываются
фактическиpromise.then(onFulfilled, onRejected)
существуетonFulfilled
Если в обратном вызове возникает исключение, вonRejected
Он не может запечатлеть это необычное, используйтеcatch
Исключение предыдущего onFulfilled можно поймать
На самом деле это не недостаток, можно добавить еще один в концеthen
достичь иcatch
Тот же эффект, как показано ниже
Promise.reject(1)
.then(() => {
console.log("我是对的")
})
.then(null, err => {
console.log("err:" + err) // err:1
})
// 等价于
Promise.reject(1)
.then(() => {
console.log("我是对的")
})
.catch(err => {
console.log("err:" + err) // err:1
})
В этом разница, но большинству людей нравится использовать его напрямую.catch
больше ничего
Возникла ошибка, а затем необработанная
Если возникает ошибка, то без обработки ошибки (перехвата) она останется в состоянии отклонения до тех пор, пока ошибка не будет обнаружена.
Давайте посмотрим на кусок кода
Promise.resolve()
.then(()=>{
console.log(a)
console.log("Task 1");
})
.then(()=>{
console.log("Task 2");
})
.catch((err)=>{
console.log("err:" + err)
})
.then(()=>{
console.log("finaltask")
});
// err:ReferenceError: a is not defined
// finaltask
Давайте посмотрим на приведенный выше код, мы находимся в первомthen
Необъявленная переменная выводится в
Вывод идет первымcatch
Тогда иди последнимthen
,Первыйthen
выдает ошибку и пропускает второйthen
То есть, если мы не обработаем эту ошибку (no catch), она не будет выполнена.
См. рисунок ниже
Одним из недостатков промисов является то, что промисы не могут быть прерваны.Использование этой возможности может привести к тому, что промисы прервут выполнение, что тоже является способом.
Ошибка, вызванная асинхронным обратным вызовом, не может быть обнаружена
Во-первых, давайте рассмотрим генерацию ошибок непосредственно в функции-обработчике объекта Promise.
const p = new Promise((resolve, reject)=>{
throw new Error('这是一个错误')
});
p.catch((error)=>{ console.log(error) });
Согласно вышеуказанному контенту, ошибка брошена непосредственно в обработчике объекта обещания,catch
Может быть захвачен
В следующем коде смоделируйте асинхронную ошибку в функции-обработчике объекта Promise.
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{ throw new Error('这是一个错误') }, 0)
});
p.catch((error)=>{ console.log(error) });
эта ситуацияcatch
Это не может быть захвачено, почему это? Сначала подумай, потом увидишь, потом сделаешь
причина
Список циклов событий JS разделен на макрозадачи и микрозадачи, setTimeOut — макрозадача, promise — микрозадача, а порядок выполнения разный.
Таким образом, порядок выполнения этого кода:
- Стек выполнения кода входит в обещание, запускает setTimeOut, а функция обратного вызова setTimeOut входит в очередь задач макроса.
- Код выполняет метод catch обещания и попадает в очередь микрозадач, в это время обратный вызов setTimeOut еще не выполнен.
- Проверка стека выполнения обнаруживает, что текущая очередь микрозадач выполняется, и начинает выполняться очередь макрозадач.
- воплощать в жизнь
throw new Error('这是一个错误')
В этот момент исключение фактически выбрасывается за пределы промиса.
решить
использоватьtry catch
Перехватывать исключения и активно запускатьreject
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
try{
throw new Error('这是一个错误')
}catch(e){
reject(e)
}
}, 0)
});
p.catch((error)=>{ console.log(error) });
Написанные от руки обещания - обещания/A+ совместимые
Зачем писать обещания от руки
Логика исходного кода Promise относительно не проста, мы можем только использовать его, но не знаем его принципа.
Реализация самостоятельно углубит наше понимание промисов и укрепит наши навыки работы с JS.
Более того, рукописная реализация Promise — это классический вопрос для фронтенд-интервью, поэтому здесь нет необходимости говорить больше.
Promises/A+
Разобравшись с основами использования Promise, давайте шаг за шагом реализуем Promise.
Стандарт Promises/A+ — это открытый, надежный и универсальный стандарт JavaScript Promise, сформулированный разработчиками для справки.
Многие трехсторонние библиотеки Promise реализованы в соответствии со стандартом Promises/A+.
Итак, на этот раз мы реализуем строгие стандарты Promises/A+, включая использование тестовых пакетов, предоставленных сообществом открытого исходного кода, для тестирования после завершения.
Проще говоря, если тест пройден, этого достаточно, чтобы доказать, что код соответствует стандарту Promises/A+, является законным и может быть предоставлен другим пользователям в Интернете.
Дополнительные стандарты Promises/A+ см. по ссылкам [6] [7]
Способ возведения основного фундамента
Использование Promise подробно описано выше, если мы внимательно прочитаем, то узнаем
- Обещания имеют три государства: в ожидании, разрешены / выполнены и отклонены
- Promise — это конструктор, при создании экземпляра Promise функция передается как обработчик.
- Функция-обработчик имеет два параметра (разрешить и отклонить), чтобы преобразовать результат в состояние успеха и состояние отказа соответственно.
- Если объект Promise успешно выполнен, должен быть результат, который передается через разрешение, в случае неудачи причина неудачи передается через отклонение.
- Метод then определен в прототипе Promise.
Затем в соответствии с нашими известными выше требованиями мы можем написать базовую структуру (тысячи способов, вы также можете использовать класс, если вам нравится класс)
function Promise(executor) {
// 状态描述 pending resolved rejected
this.state = "pending"
// 成功结果
this.value = undefined
// 失败原因
this.reason = undefined
function resolve(value) {}
function reject(reason) {}
}
Promise.prototype.then = function(onFulfilled, onRejected) {}
Как показано выше, мы создали конструктор Promise,state
Свойства содержат состояние объекта Promise, используйтеvalue
Свойство содержит результат успешного выполнения объекта Promise, и используется причина сбоя.reason
Сохранение атрибутов, эти имена полностью соответствуют стандартам Promises/A+
Затем создаем в конструктореresolve
а такжеreject
два метода, затем создал один на прототипе конструктораthen
метод, готовый к использованию
Инициализировать экземпляр исполнителя для немедленного выполнения
Мы знаем, что при создании экземпляра промиса функция-обработчик (executor) будет выполняться немедленно, поэтому мы меняем код
function Promise(executor) {
this.state = "pending"
this.value = undefined
this.reason = undefined
// 让其处理器函数立即执行
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
function resolve(value) {}
function reject(reason) {}
}
реализация обратного вызова разрешить и отклонить
Спецификация Promises/A+ предусматривает, что когда объект Promise был изменен из состояния ожидания в успешное состояние (разрешено) или состояние сбоя (отклонено), состояние не может быть изменено снова, то есть состояние не может быть обновлено. после успеха или неудачи.
Поэтому нам нужно судить при обновлении состояния.Если текущее состояние находится в состоянии ожидания (состояние ожидания), его можно обновить, поэтому мы можем его улучшить.resolve
а такжеreject
метод
let _this = this
function resolve(value) {
if (_this.state === "pending") {
_this.value = value
_this.state = "resolved"
}
}
function reject(reason) {
if (_this.state === "pending") {
_this.reason = reason
_this.state = "rejected"
}
}
Как показано выше, сначала мы используем переменную внутри конструктора Promise._this
Конструктор хостингаthis
Тогда мыresolve
а такжеreject
В функцию добавлено суждение, потому что операция изменения состояния может быть выполнена только в том случае, если текущее состояние находится в состоянии ожидания.
В то же время сохраните результат успеха и причину отказа в соответствующем атрибуте.
Затем установите свойство состояния в обновленное состояние.
затем базовая реализация метода
Тогда мы просто реализуемthen
метод
первыйthen
Метод имеет два обратных вызова, при изменении состояния Promise будет вызываться успех или неудача соответственно.then
Два обратных вызова метода
Таким образом, реализация метода then выглядит достаточно просто: вы можете вызывать разные callback-функции в зависимости от состояния.
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.state === "resolved") {
if (typeof onFulfilled === "function") {
onFulfilled(this.value)
}
}
if (this.state === "rejected") {
if (typeof onRejected === "function") {
onRejected(this.reason)
}
}
}
Как показано выше, посколькуonFulfilled & onRejected
Оба параметра не являются обязательными параметрами, поэтому мы судим о типе параметра после оценки состояния.Если параметр не является функциональным типом, он не будет выполнен, потому что нефункциональный тип, определенный в спецификации Promises/A+, можно игнорировать.
Обещания поддерживают асинхронность
Пишем здесь, мы можем подумать, а? Промис реализовать несложно, скоро он будет выглядеть так, давайте вкратце его протестируем.
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then(data => console.log(data)) // 1
Что ж, как и ожидалось, снова попробуем асинхронный код.
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1000);
})
p.then(data => console.log(data)) // 无输出
Проблема в том, что Promise, асинхронное решение, написанное нами, не поддерживает асинхронность. . .
Давайте проанализируем, изначально он был выполнен через 1000 мс.then
метод, запустите приведенный выше код и обнаружите, что результата нет, в чем проблема?
Функция setTimeout позволяетresolve
Становится асинхронным выполнением, с задержкой, вызовомthen
метод, текущее состояние все еще находится в ожидании.then
метод, который не вызываетсяonFulfilled
тоже не звонилonRejected
Ну, мы начали преобразование, зная причину.Если бы это были вы, как бы вы ее решили?Вы можете подумать об этом 40 секунд и придумать общую идею, которую можно реализовать.
намекать:Вы можете обратиться к модели публикации-подписки выше.Если нет идеи за 40 секунд, что ж, ее нужно улучшить.
|
|
--> Чтобы дать вам немного потренировать левое полушарие и активировать атмосферу, я также приложил большие усилия (ps:Я это уже видел, скучно без лайка и поощрения 😄)
|
|
Вернемся к теме, давайте решим эту проблему
Мы можем обратиться к модели публикации-подписки и выполнитьthen
Если метод все еще находится в состоянии ожидания (pending), временно сохраните callback-функцию в очереди (то есть массиве), а при изменении состояния вытащите ее из массива и выполните по очереди.
Есть идея, давайте ее реализовывать
Прежде всего, нам нужно добавить в конструктор два массива типа Array для хранения callback-функций успеха и неудачи.
function Promise(executor) {
let _this = this
this.state = "pending"
this.value = undefined
this.reason = undefined
// 保存成功回调
this.onResolvedCallbacks = []
// 保存失败回调
this.onRejectedCallbacks = []
// ...
}
нам еще нужно улучшитьthen
метод, вthen
Когда метод выполняется, если состояние находится в состоянии ожидания, его функция обратного вызова сохраняется в соответствующем массиве
Promise.prototype.then = function(onFulfilled, onRejected) {
// 新增等待态判断,此时异步代码还未走完,回调入数组队列
if (this.state === "pending") {
if (typeof onFulfilled === "function") {
this.onResolvedCallbacks.push(onFulfilled)
}
if (typeof onRejected === "function") {
this.onRejectedCallbacks.push(onRejected)
}
}
// 以下为之前代码块
if (this.state === "resolved") {
if (typeof onFulfilled === "function") {
onFulfilled(this.value)
}
}
if (this.state === "rejected") {
if (typeof onRejected === "function") {
onRejected(this.reason)
}
}
}
Как показано выше, мы перепишемthen
метод, в дополнение к оценке состояния успеха (разрешено) и состояния сбоя (отклонено), мы добавили ожидание состояния (ожидание), когда состояние является состоянием ожидания, асинхронный код не завершен, затем мы сохраняем соответствующий обратный вызов сначала в подготовленный массив
В конце дня должен быть выполнен последний шаг, мы находимся вresolve
а такжеreject
метод можно назвать
function resolve(value) {
if (_this.state === 'pending') {
_this.value = value
// 遍历执行成功回调
_this.onResolvedCallbacks.forEach(cb => cb(value))
_this.state = 'resolved'
}
}
function reject(reason) {
if (_this.state === 'pending') {
_this.reason = reason
// 遍历执行失败回调
_this.onRejectedCallbacks.forEach(cb => cb(reason))
_this.state = 'rejected'
}
}
На данный момент мы реализовали асинхронное разрешение Promise, поторопитесь и протестируйте его
Реализовать классический связанный вызов Promise
Обещанияthen
Метод можно вызывать в цепочке, что является одной из сути Promise, и это более сложное место для реализации.
Сначала мы должны понятьthen
Каковы требования? Это требует внимательного изучения спецификации Promises/A+ дляthen
Определение возвращаемого значения метода и процесс разрешения промисов, конечно, если вы внимательно прочитали вышеизложенное.then
Использование метода, вероятно, понятно, и мы снова резюмируем его здесь.
-
первый
then
метод должен возвращатьpromise
объект (акцент) -
если
then
Если метод возвращает общее значение (такое как Number, String и т. д.), используйте это значение, чтобы обернуть его в новый объект Promise и вернуть его. -
если
then
метод неreturn
оператор, он возвращает объект Promise, завернутый в Undefined -
если
then
Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.then
onRejected -
если
then
Если метод не проходит ни в один обратный вызов, он продолжает проходить вниз (проникновение значения) -
если
then
Метод возвращает объект Promise, тогда этот объект имеет преимущественную силу и возвращает свой результат.
Что ж, на этом наши требования прояснились, и началась реализация кода.
Требование гласит, что еслиthen
Метод не проходит ни в один обратный вызов, он продолжает передаваться, но каждыйthen
возвращает новое обещание, то есть когдаthen
Когда в методе нет обратного вызова, нам нужно продолжать передавать полученное значение вниз.Это на самом деле легко обработать.Нам нужно только превратить его в функцию обратного вызова и вернуть обычное значение, когда мы решим, что параметр обратного вызова не функция.
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err }
// ...
}
над намиthen
В реализации в каждый исполняемый файл добавлена проверка типа, является ли параметр функцией, но мы здесьthen
В начале метода проверка выполняется единообразно, поэтому нет необходимости в проверке параметров.
настоящее времяthen
метод становится
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err }
if (this.state === "pending") {
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
if (this.state === "resolved") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.reason)
}
}
следующий
так как каждыйthne
вернуть новое обещание, то мы сначалаthen
Создайте экземпляр Promise в возврате, запустите преобразование
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err }
let promise2 = new Promise((resolve, reject) => {
if (this.state === "pending") {
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
if (this.state === "resolved") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.reason)
}
})
return promise2
}
мы вthen
В методе экземпляр объекта Promise создается и возвращается, и мы помещаем исходный код в функцию-обработчик экземпляра.
Затем при каждой функции выполнения используйтеtry..catch
синтаксис, попробуйresolve
Результат выполнения, в уловеreject
Исключение, оригиналthen
В методе есть три логических суждения: разрешено, отклонено и ожидается, а именно:
Когда оценивается разрешенное состояние, логика отклонения и разрешения непротиворечива.
if (this.state === "resolved") {
try {
// 拿到返回值resolve出去
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
// catch捕获异常reject抛出
reject(e)
}
}
Логика отложенного решения о состоянии также аналогична разрешенной.Однако, чтобы справиться с асинхронностью, мы сделали здесь операцию push, поэтому, когда мы push, мы можем установить обратный вызов вне обратных вызовов onFulfilled и onRejected для работы, которые все идиомы JS.
if (this.state === "pending") {
// push(onFulfilled)
// push(()=>{ onFulfilled() })
// 上面两种执行效果一致,后者可在回调中加一些其他功能,如下
this.onResolvedCallbacks.push(() => {
try {
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
}
Далее начинаем обработку по предыдущемуthen
Возвращаемое значение метода используется для генерации нового объекта Promise.Эта логика более сложная.Для этого можно извлечь метод из спецификации.Давайте сделаем это.
/**
* 解析then返回值与新Promise对象
* @param {Object} 新的Promise对象,就是我们创建的promise2实例
* @param {*} x 上一个then的返回值
* @param {Function} resolve promise2处理器函数的resolve
* @param {Function} reject promise2处理器函数的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
// ...
}
Давайте проанализируем и улучшим функцию resolvePromise шаг за шагом
Избегайте циклических ссылок.Когда возвращаемое значение then совпадает с вновь созданным объектом Promise (тот же ссылочный адрес), будет выброшено исключение TypeError.
пример:
let promise2 = p.then(data => {
return promise2;
})
// TypeError: Chaining cycle detected for promise #<Promise>
Если он возвращает свой собственный объект Promise, состояние всегда находится в состоянии ожидания, и оно больше не может быть разрешено или отклонено, и программа умрет, поэтому ее необходимо обработать в первую очередь.
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('请避免Promise循环引用'))
}
}
Определите тип x и обработайте его в зависимости от ситуации
Когда x — обещание, выполнить его, успех — это успех, неудача — это провал, еслиx
является объектом или функцией и обрабатывается дальше, иначе это нормальное значение
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('请避免Promise循环引用'))
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是个对象或是函数
} else {
// 是个普通值
resolve(x)
}
}
Если x является объектом, попробуйте применить метод then к объекту. Если в это время сообщается об ошибке, преобразуйте promise2 в состояние сбоя.
Уловка здесь для предотвращения ошибок заключается в том, что существует много реализаций Promise, предполагая, что объект Promise, реализованный другим человеком, используетObject.defineProperty()
Выбрасывая ошибку при взятии значения, мы можем предотвратить ошибки в коде
// resolvePromise方法内部片段
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是个对象或是函数
try {
// 尝试取出then方法引用
let then = x.then
} catch (e) {
reject(e)
}
} else {
// 是个普通值
resolve(x)
}
если объект имеетthen
,а такжеthen
является типом функции, его можно рассматривать как объект Promise, после чего используйтеx
как это вызвать выполнениеthen
метод
// resolvePromise方法内部片段
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是个对象或是函数
try {
// 尝试取出then方法引用
let then = x.then
if (typeof then === 'function') {
// then是function,那么执行Promise
then.call(x, (y) => {
resolve(y)
}, (r) => {
reject(r);
})
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
// 是个普通值
resolve(x)
}
На этом этапе нам также необходимо рассмотреть ситуацию, если объект Promise преобразуется в успешное состояние или объект Promise передается при сбое, он должен продолжать выполняться до тех пор, пока не будет выполнено последнее обещание, например следующее:
Promise.resolve(1).then(data => {
return new Promise((resolve,reject)=>{
// resolve传入的还是Promise
resolve(new Promise((resolve,reject)=>{
resolve(2)
}))
})
})
Чтобы решить эту ситуацию, мы можем использовать рекурсию и переписать вызов для разрешения, чтобы рекурсивно выполнить resolvePromise, чтобы он не завершался до тех пор, пока промис не будет преобразован в нормальное значение.
// resolvePromise方法内部片段
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是个对象或是函数
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
// 递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
// 普通值结束递归
resolve(x)
}
В спецификации определено, что если вызываются и resolvePromise, и rejectPromise, или один и тот же параметр вызывается несколько раз, первый вызов имеет приоритет, а все последующие вызовы будут игнорироваться.Чтобы сделать только один вызов для успеха и неудачи, мы продолжаем совершенствоваться, установите Set a call для предотвращения множественных вызовов
// resolvePromise方法内部片段
let called
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 可能是个对象或是函数
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return
called = true
// 递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// 普通值结束递归
resolve(x)
}
На данный момент мы достиглиresolvePromise
метод, давайте вызовем его, чтобы реализовать полныйthen
метод, в оригинальном методе-прототипеthen
в насreturn
Создается обещание2, и среди трех оценок состояния функции обработчика экземпляраresolve
заменяетсяresolvePromise
метод
Затем, в это времяthen
Реализация метода завершена?
Конечно пока нет, все мы знаем, что функция процессора в Promise выполняется синхронно, иthen
Метод асинхронный, но делаем это синхронно
Решить эту проблему на самом деле очень просто.Следуя практике большинства библиотек Promise на рынке, мы используем setTimeout для моделирования.then
Используйте setTimeout, чтобы стать асинхронным везде при выполнении метода (единственная разница между этим и собственными промисами браузера заключается в том, что промис браузера..then является микрозадачей, и мы используем setTimeout для реализации ее как макрозадачи), но Это также то, что делает большинство библиотек Promise, а именно:
setTimeout(() => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
},0)
Теперь наше окончательное изданиеthen
метод выполнен
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
let promise2 = new Promise((resolve, reject) => {
// 等待态判断,此时异步代码还未走完,回调入数组队列
if (this.state === "pending") {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
if (this.state === "resolved") {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === "rejected") {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
})
return promise2
}
поймать реализацию
реализует самые сложныеthen
После метода,catch
Реализация очень проста, вы можете понять это с первого взгляда
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
проверка кода
Сообщество с открытым исходным кодом предоставляет пакет для тестирования нашего кода на соответствие Promises/A+:promises-aplus-tests
Сначала мы должны предоставить тестовому пакетуdeferred
крючок для тестирования
Таким образом, следующий код предотвратит нашеPromise.js
Файл находится в конце файла
// promises-aplus-tests测试
Promise.defer = Promise.deferred = function() {
let defer = {}
defer.promise = new Promise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
try {
module.exports = Promise
} catch (e) {}
Затем установите этот пакет
npm install promises-aplus-tests -D
выполнить тест
npx promises-aplus-tests Promise.js
Подождите некоторое время, если консоль не станет популярной, она успешна и соответствует спецификациям, как показано на рисунке.
полный код
Длина уже очень большая, и в продолжении есть другой контент, поэтому реализовано больше основных методов Promise, а затем и catch.
Другие разрешения/отклонения/гонки/все и т. д. относительно просты и здесь не описываются.
Я выложу адрес реализации нескольких методов Promise на своей стороне.Если вам интересно прочитать код самостоятельно, комментарии очень подробные, и там около 200 строк кода.
- Гитхаб:GitHub.com/is boy JC/pro…
Обещай хорошо/плохо
преимущество
Промисы пишут асинхронный код синхронно, избегая уровней вложенных функций обратного вызова.
Объекты Promise предоставляют унифицированный интерфейс, упрощающий управление асинхронными операциями.
Цепная операция, вы можете продолжить запись объекта Promise в then и return, а затем продолжить вызов then для выполнения операции обратного вызова
недостаток
Как только объект Promise будет создан, он будет выполнен немедленно и не может быть отменен на полпути.
Если функция обратного вызова не задана, внутри промиса будет выброшена ошибка, которая не будет вытекать наружу.
Когда он находится в состоянии ожидания, невозможно узнать, на каком этапе он находится в данный момент.
После использования слишком большого количества промисов код с первого взгляда выглядит как API промисов, а синтаксис цепочки всегда кажется уродливым и неэлегантным.
Generator
Генератор — это реализация сопрограммы в ES6.Самая большая особенность заключается в том, что он может передавать право выполнения функции.
Мы можем приостановить поток выполнения функции с помощью ключевого слова yield, которое дает возможность изменить поток выполнения, тем самым предоставляя решение для асинхронного программирования.
Генератор по-английски это генератор
Если вы хотите разобраться в генераторе (Generator), или не можете обойти понятие итератора (Iterator), давайте кратко познакомим
Итератор
Введение в итератор
Итератор — это интерфейс, его также можно назвать спецификацией.
Разные типы данных в js, такие как (Array/Object/Set), имеют разные методы обхода, например, мы будем использовать обход объектов.for..in..
, можно использовать массивfor循环/for..in../forEach
так далее
Итак, существует ли единый способ прохождения этих данных? В этом смысл итераторов. Они могут обеспечить унифицированный способ обхода данных. Просто добавьте свойство, поддерживающее итераторы, в структуру данных, которую вы хотите обходить.
Синтаксис итератора
const obj = {
[Symbol.iterator]:function(){}
}
[Symbol.iterator]
Имя атрибута является фиксированным способом написания.Пока объект с атрибутом принадлежит, он может быть пройден итератором.
Метод обхода итератора состоит в том, чтобы сначала получить указатель на итератор, который изначально указывает на перед первым фрагментом данных.
Затем, позвонивnext
метод, измените указатель, чтобы он указывал на следующую часть данных
каждый разnext
Он возвращает объект, который имеет два свойства
-
значение представляет данные, которые вы хотите получить
-
done Логическое значение, false указывает, что данные, на которые указывает текущий указатель, имеют значение, а true указывает, что обход завершен
Подробный итератор
В JS,Array/Set/Map/String
Оба поддерживают итераторы по умолчанию.
Поскольку и массивы, и коллекции поддерживают итераторы, их можно обходить одинаково.
ES6 предоставляет новый метод цикла, называемыйfor-of
он на самом деле использует итератор для прохождения
Другими словами, можно использовать только те структуры данных, которые поддерживают итераторы.for-of
цикл
Обход массива с помощью итераторов
let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next()) // { value: Object { num: 1 }, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: 3, done: false }
console.log(it.next()) // { value: undefined, done: true }
Массивы поддерживают обход итераторов, поэтому вы можете напрямую получать их итераторы, и то же самое касается коллекций.
Обход коллекции с помощью итераторов
let list = new Set([1,3,2,3])
let it = list.entries() // 获取set集合中自带的的迭代器
console.log(it.next()) // { value: [ 1, 1 ], done: false }
console.log(it.next()) // { value: [ 3, 3 ], done: false }
console.log(it.next()) // { value: [ 2, 2 ], done: false }
console.log(it.next()) // { value: undefined, done: true }
Наборы отличаются от массивов тем, что мы можем использоватьentries
способ получить итератор
Значение, проходимое каждый раз в коллекции Set, представляет собой массив, и первый и второй элементы в нем совпадают.
Использование итераторов для обхода пользовательских объектов
Прежде всего, пользовательский объект не имеет атрибутов итератора, поэтому, как мы все знаем, итерация не поддерживается.for..of
Обойти объект невозможно, причина здесь, т.к.for..of
заключается в использовании итераторов для итерации, поэтому объекты нельзя использоватьfor..of
Теперь, когда мы знаем, что это происходит из-за того, что пользовательский объект не имеет свойства итератора, мы можем добавить его вSymbol.iterator
такое свойство и реализовать для него метод итератора следующим образом
let obj = {
name : 'tom',
age : 18,
gender : '男',
intro : function(){
console.log('my name is '+this.name)
},
[Symbol.iterator]:function(){
let i = 0;
// 获取当前对象的所有属性并形成一个数组
let keys = Object.keys(this);
return {
next: function(){
return {
// 外部每次执行next都能得到数组中的第i个元素
value:keys[i++],
// 如果数组的数据已经遍历完则返回true
done:i > keys.length
}
}
}
}
}
for(let attr of obj){
console.log(attr);
}
Как показано выше, плюс[Symbol.iterator]
Для этого свойства итератора мы настроили метод итератора, который можно использоватьfor..of
метод
Роль итератора
Итератор имеет три роли:
- Обеспечить единый и удобный интерфейс доступа к различным структурам данных
- Позволяет расположить элементы структуры данных в определенном порядке.
- ES6 создает новую команду обхода
for..of
Loop, интерфейс Iterator в основном используется дляfor..of
Потребление
Давайте представим здесь Iterator, давайте поймем, что такое параметр Iterator выше, то есть он представляет собой параметр со свойствами итератора
Знакомство с генератором
Генератор на самом деле функция, просто специальная функция
Обычная функция, вы запускаете эту функцию, функция не остановится до конца функции
Особенность функции генератора в том, что ее можно остановить в середине.
Особенности функции генератора
function *generatorFn() {
console.log("a");
yield '1';
console.log("b");
yield '2';
console.log("c");
return '3';
}
let it = generatorFn()
it.next()
it.next()
it.next()
it.next()
Приведенный выше пример представляет собой функцию генератора.Сначала мы наблюдаем за его характеристиками и анализируем их одну за другой.
- В отличие от обычных функций, генераторные функции
function
После этого перед именем функции стоит*
-
*
Используется для указания того, что функция является функцией-генератором. - Существует множество правописаний,
function* fn()
,function*fn()
а такжеfunction *fn()
все будет хорошо
-
- Внутри функции есть
yield
поле-
yield
Используется для определения состояния внутри функции и отказа от права на выполнение - Это ключевое слово может появляться только в теле функции-генератора, но в генераторе не может быть ключевого слова yield.Когда функция встречает yield, она приостанавливается и возвращает результат выражения после yield.
-
- После вызова используется возвращаемое значение его функции
next
метод- Вызов функции-генератора аналогичен вызову обычной функции, просто добавьте () после имени функции.
- Функции-генераторы не выполняются немедленно, как обычные функции, а возвращают указатель на объект внутреннего состояния.
- Итак, чтобы вызвать объект итератора Iterator
next
метод указатель начнет выполнение с начала функции или с того места, где он остановился -
next
На самом деле метод заключается в том, чтобы вернуть управление кодом функции-генератору.
Процесс исполнения анализа
Затем мы анализируем процесс его выполнения, строку, чтобы увидеть результаты его печати, или пример выше.
let it = generatorFn()
it.next()
// a
// {value: "1", done: false}
it.next()
// b
// {value: "1", done: false}
it.next()
// c
// {value: "1", done: true}
it.next()
// {value: undefined, done: true}
Во-первых, функция Generator выполняется, возвращая указатель на объект внутреннего состояния без каких-либо выходных данных в этот момент.
первый звонокnext
метод, который начинается с головы функции генератора, сначала печатает и выполняется до тех пор, покаyield
стоп, иyield
В качестве значения свойства value возвращаемого объекта используется значение "1" следующего выражения. В настоящее время функция не выполнена, а свойство done возвращенного объекта равно false.
второй звонокnext
способ тот же, что и выше
третий звонокnext
метод, сначала напечатайте c , затем выполните операцию возврата функции и используйте значение выражения после возврата в качестве значения возвращаемого объекта, В этот момент функция завершилась, поэтому значение атрибута done равно true
четвертый звонокnext
метод, функция была выполнена в это время, поэтому значение атрибута возвращаемого значения не определено, а значение атрибута done равно true.Если при выполнении третьего шага нет оператора возврата, он вернется напрямую.{value: undefined, done: true}
Простое понимание, функция генератораyield
Он останавливается там, где находится, и используется при вызовеnext
метод шаг за шагом
передача следующего параметра
yield
имеет возвращаемое значение,next
Способ не имеет прямого вызова, когда входящие параметры,yield
Возвращаемое значение выражения не определено
Когда передается следующий параметр, этот параметр будет использоваться какПредыдущийyield
Возвращаемое значение
Мы понимаем на примере
function *geFn(){
cosnole.log("start")
let a = yield "1"
console.log(a)
let b = yield "2"
console.log(b)
let c = yield "3"
console.log(c)
return 4
}
let it = geFn()
it.next()
// start
// { value:1, done: false }
it1.next()
// undefined 未传值,所以a=undefined
// { value:2, done: false }
it.next("hahaha")
// hahaha 传值,所以b=hahaha
// { value:3, done: false }
it.next("omg")
// omg 传值,所以c=omg
// {value: 4, done: true}
из-заnext
Параметр метода представляет предыдущийyield
Возвращаемое значение оператора, поэтому первое использованиеnext
метод без параметров
Двигатель V8 будет напрямую игнорировать первое использованиеnext
метод, когда параметры используются только со второго разаnext
Метод запускается, параметры действительны
Когда значение не получено,yield
Возвращаемое значение оператора не определено, как показано в выводе приведенного выше примера.
пройти черезnext
Параметры метода, есть способ продолжить инъекцию значений в тело функции после запуска функции Генератора, а это значит, что мы можем инжектить разные значения извне внутрь на разных этапах Генератора функция работает, чтобы настроить поведение функции.
снова понять доходность
Давайте посмотрим на другой фрагмент кода, который поможет нам понятьyield
function *geFn(){
console.log("start")
let a = yield console.log("1")
console.log(a)
let b = yield console.log("2")
console.log(b)
return console.log("3")
}
let it = geFn()
it.next()
// start
// 1
// {value: undefined, done: false}
it.next("我是a")
// 我是a
// 2
// {value: undefined, done: false}
it.next("我是b")
// 我是b
// 3
// {value: undefined, done: true}
пройти черезnext
Позвонив мы видим, что выводится первый вызовstart & 1
,иметь в видуyield
При остановке после выполнения кода в
Как показано на изображении выше, если он скажетyield
Сравните это со стеной, тогда правая сторона стены и верхняя часть составляют одну часть, а левая сторона и нижняя часть стены составляют одну часть, так что это должно быть достаточно просто.
for..of итерирует генератор
Мы знаем вышеfor...of
Внутренняя реализация заключается в использовании итератора для итерации, затемfor...of
Разве не было бы идеально использовать цикл непосредственно в итераторе Generator?
Да, он может автоматически проходить функцию Генератора, и в это время нет необходимости вызывать следующий метод.Как только свойство done возвращаемого объекта следующего метода становится истинным,for...of
цикл прервется без возвращаемого объекта
function *foo() {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (let v of foo()) {
console.log(v)
}
// 1 2 3 4 5
выражения yield*
существуетyield
За командой следует звездочка, указывающая, что она возвращает обходчик, который называетсяyield*
выражение
function *foo(){
yield "foo1"
yield "foo2"
}
function *bar(){
yield "bar1"
yield* foo()
yield "bar2"
}
for(let val of bar()){
console.log(val)
}
// bar1 foo1 foo2 bar2
yield
Если за командой не следует звездочка, возвращается весь массив, а звездочка указывает, что возвращаемый массив является обходчиком.
function* gen1(){
yield ["a", "b", "c"]
}
for(let val of gen1()){
console.log(a)
}
// ["a", "b", "c"]
// ------------------- 上下分割
function* gen2(){
yield* ["a", "b", "c"]
}
for(let val of gen2()){
console.log(a)
}
// a b c
Генератор взамен
Метод возврата возвращает заданное значение и заканчивает обход функции генератора
Когда return не имеет значения, return undefined, см. пример
function* foo(){
yield 1
yield 2
yield 3
}
var f = foo()
f.next()
// {value: 1, done: false}
f.return("hahaha")
// 由于调用了return方法,所以遍历已结束,done变true
// {value: "hahaha", done: true}
f.next()
// {value: undefined, done: true}
Выброс обработки ошибок генератора
throw
Попасть в яму, если не быть осторожным, довольно легко, рассмотрим несколько примеров.
function *foo(){
try{
yield 'hahaha'
}catch(err){
console.log('inside error: ' + err)
}
}
var f = foo()
try{
it.throw('this is err')
}catch(err){
console.log('out error: ' + err)
}
Какую ошибку выведет приведенный выше код?
На самом деле ответ очень прост, приведенный выше код выведетout error:this is err
потому что звонюthrow
, мы не реализовалиnext
метод, на этот раз внутреннийtry{}catch{}
Код еще не был выполнен, поэтому он будет перехвачен только снаружи.
Итак, нам просто нужно позвонитьthrow
Перед повторным вызовомnext
, в это время тело функции было выполненоtry{}catch{}
, затем выполните дляthrow
, как внутри, так и снаружи перехвата ошибок,throw
Сначала метод будет захвачен внутри, тем самым печатаяinside error:this is err
Кроме того,throw
метод выполнит следующийyield
, посмотрим пример
var foo = function* foo(){
try {
yield console.log('1')
yield console.log('2')
} catch (e) {
console.log('inside err')
}
yield console.log('3')
yield console.log('4')
}
var g = foo()
g.next()
g.throw()
g.next()
Давайте посмотрим на процесс выполнения приведенного выше кода.
сначала выполнить первыйnext
метод, введитеtry()catch()
, выход 1
Далее выполнитеthrow
метод, внутренний захват, выводinside err
,В настоящее времяtry()catch()
блок кода был выполненcatch
,try()catch()
Блок кода закончился, так что случайно выполнитеyield
Мы будем продолжать смотреть вниз, поэтому реэкспорт 3
окончательное исполнениеnext
метод, выход 4
Конечный результат1 3 4
Расширение генератора
В начале Генератора есть предложение, не знаю, понимаете ли вы его.
- Генератор — это реализация сопрограммы в ES6.Самая большая особенность заключается в том, что он может передавать право выполнения функции.
Что такое корутина?
Здесь используется объяснение сопрограммы из ссылки на статью Учителя Жуань Ифэна [8], которое немного изменено и дополнено.
Каждый должен знать о процессах и потоках, так что же такое сопрограмма?
Я не знаю, знаете ли вы, что потоки пользовательского пространства на самом деле являются своего рода потоком, который программист пишет программы для управления своим планированием, невидимым для ядра.
Сопрограмму можно понимать как «поток пользовательского пространства» или как несколько «потоков», взаимодействующих друг с другом для выполнения асинхронных задач.
Поскольку поток — это наименьшая исполнительная единица операционной системы, можно также сделать вывод, что сопрограмма реализована на основе потока, но она намного легче, чем поток.
Есть несколько характеристик:
- Сотрудничество, потому что это политика планирования, написанная самим программистом, которая переключается посредством сотрудничества, а не вытеснения.
- Создавайте, переключайте и уничтожайте в пользовательском режиме
- С точки зрения программирования идея сопрограмм — это, по сути, активные механизмы выхода и возобновления потока управления.
Процесс его работы следующий
- Сопрограмма A начинает выполняться
- Горутина A находится на полпути к выполнению, приостанавливает выполнение и передает право на выполнение горутине B.
- Через некоторое время B возвращает право на исполнение
- Корутина A восстанавливает право на выполнение и продолжает выполняться
Вышеупомянутая сопрограмма A является асинхронной задачей, потому что право на выполнение крадет B в процессе выполнения, и она вынуждена выполняться в два этапа.
Например, сопрограмма для чтения файла записывается следующим образом
function asnycJob() {
// ...其他代码
var f = yield readFile(fileA)
// ...其他代码
}
Функция asyncJob в приведенном выше коде является сопрограммой, в которойyield
команда, значит, исполнение здесь, а право исполнения будет передано другим сопрограммам, т. е.yield
Команда — это разделительная линия между двумя фазами асинхронности.
Сопрограммы встречаютсяyield
Команда приостанавливается, ждет, пока вернется право на выполнение, а затем возобновляет выполнение с того места, где она была приостановлена.Ее самое большое преимущество в том, что код написан очень похоже на синхронную операцию, только с одним дополнительнымyield
Заказ
Генератор и сопрограмма
JS однопоточный Реализация генератора в ES6 аналогична открытию нескольких потоков, но он по-прежнему может запускать только один поток за раз, но его можно переключать.
Точно так же, как автомобиль, движущийся по шоссе, шоссе js — это только улица с односторонним движением (основной поток), но есть много полос (вспомогательный поток), которые можно объединить в поток трафика (после завершения асинхронной задачи обратный вызов входит в очередь задач основного потока)
Генератор превращает js магистраль в многополосную (реализация сопрограммы), но при этом может ехать только одна машина по одной полосе (поэтому она все равно однопоточная), но может свободно менять полосы (управление хендовером)
Преобразователь функции генератора
Рождение функции thunk связано с проблемой дизайна компилятора:求值策略
, то есть, когда именно должны оцениваться параметры функции
var x = 1
function fn(n) {
return n * 10
}
fn(x + 5)
Как показано выше, при вызове метода fnx+5
Когда следует оценивать это выражение, есть две идеи
-
вызов по значению, сначала вычислить
x+5
значение, затем преобразовать это значение6
Передача функции fn, например в языке C, имеет то преимущество, что ее относительно просто реализовать, но это может привести к потерям производительности (например, функция передает два параметра, а второй параметр является выражением, но есть нет функции в теле.Использование этого параметра, а затем вычисление значения сначала потеряет производительность и будет бессмысленным) -
называть по имени, то есть напрямую преобразовать выражение
x+5
Передайте тело функции и оценивайте его только тогда, когда оно используется
Определение функции Thunk представляет собой стратегию реализации вызова по имени, которое используется для замены выражения.Идея реализации на самом деле очень проста.
Сначала поместите параметры во временную функцию, а затем передайте временную функцию в тело функции, как показано ниже.
function fn(m){
return m * 2
}
fn(x + 5)
// thunk实现思路
var thunk = function () {
return x + 5
}
function fn(thunk){
return thunk() * 2
}
JS — это вызов по значению, а его функция Thunck имеет другое значение
В JS функция Thunk — это не выражение, а применение курриизации функции. Короче говоря, это замена функции с несколькими параметрами на функцию с одним параметром, которая принимает только функцию обратного вызова в качестве параметра. Давайте посмотрим Простая реализация этого
fs.readFile(fileName, callback)
const Thunk = function(fn){
return function(...args){
return function(callback){
return fn.call(this,...args,callback)
}
}
}
// 使用上面的Thunk转化器,生成fs.readFile的Thunk函数
var readFileThunk = Thunk(fs.readFile)
readFileThunk(fileName)(callback)
Если вы хотите использовать функцию Thunk в производственной среде, вы можете использовать модуль Thunkify.На самом деле, его основной исходный код — это Thunk, который мы написали выше.Thunkify имеет дополнительный механизм проверки, который относительно прост и может быть понят Модуль Baidu Thunkify.
Thunk на самом деле не очень полезен до ES6, но после выхода функции Generator может пригодиться функция Thunk, которую можно использовать для автоматического управления процессом функции Generator, получения и обмена правами на выполнение программы.
Давайте реализуем автоматический исполнитель Generator на основе функции Thunk.
// 基于Thunk函数的Genertor函数自动执行器
function run(fn) {
let gen = fn()
function next(err, data) {
// 将指针移动到Generator函数的下一步
let result = gen.next(data)
// 判断是否结束
if (result.done) return
// 递归,把next放进.value中
result.value(next)
}
next()
}
// 模拟异步方法
let sleep = function(n, callback) {
setTimeout(() => {
console.log(n)
callback && callback(n)
}, n)
}
// 模拟异步方法进行Thunk转换
let sleepThunk = Thunk(sleep)
// Generator函数
let gen = function*() {
let f1 = yield sleepThunk(1000)
let f2 = yield sleepThunk(1500)
// ...
let fn = yield sleepThunk(2000)
}
// 调用Genertor函数自动执行器
run(gen)
Функция запуска приведенного выше кода является автоматическим исполнителем функции Generator, а внутренняя функция next является функцией обратного вызова Thunk.
Функция next сначала перемещает указатель на следующий шаг функции Generator (метод gen.next).
Затем оцените, заканчивается ли функция генератора (свойство result.done)
Если это не закончилось, передать следующую функцию в функцию Thunk (атрибут result.value), в противном случае выйти напрямую
В коде моделируется асинхронная операцияsleep
метод и преобразовать его в метод Thunk (используя упрощенную версию Thunk, которую мы реализовали выше)
Функция gen инкапсулирует n асинхронных операций, которые будут автоматически выполняться до тех пор, пока выполняется функция запуска.
Таким образом, асинхронные операции можно не только писать как синхронные операции, но и выполнять их одной строкой кода, что чрезвычайно удобно.
Но я полагаю, все видели, что функция Генератора передается этим автоматическим исполнителем,Способ возврата Доходности должен быть функциональным Thunk
--------- 👇 ----------------
Здесь кратко представлен Thunk Дополнительные рекомендации, связанные с Thunk, см. по справочной ссылке Ruan Yifeng [9].
Нам просто нужно понять, что такое Thunk и какое отношение он имеет к Generator.
Библиотека совместных функций генератора
Библиотека функций co — это небольшой инструмент, выпущенный известным программистом TJ Holowaychuk в июне 2013 года для автоматического выполнения функций Generator.
портал исходного кода библиотеки функций co
Библиотека функций co на самом деле представляет собой два типа автоматических исполнителей (функция Thunk и объект Promise), которые упакованы в библиотеку, поэтому предварительным условием использования co является то, что за командой yield функции-генератора может следовать только функция Thunk или Promise. объект.
Функция co возвращает обещание, поэтому мы можем следоватьthen
и т.д. метод
Выше был представлен автоматический исполнитель на основе функции Thunk, далее фактически аналогичен на основе Promise, просто реализуем его
// 基于Promise函数的Genertor函数自动执行器
function run(gen) {
let g = gen()
function next(data) {
// 将指针移动到Generator函数的下一步
let result = g.next(data)
// 判断是否结束,结束返回value,value是一个Promise
if (result.done) return result.value
// 递归
result.value.then(data => {
next(data)
})
}
next()
}
// 模拟异步方法进行Promise转换
let sleepPromise = function(n) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
console.log(n)
resolve(n)
}, n)
})
}
// Generator函数
let gen = function*() {
let f1 = yield sleepPromise(1000)
let f2 = yield sleepPromise(1500)
// ...
let fn = yield sleepPromise(2000)
}
// 调用Genertor函数自动执行器
run(gen)
Разница между приведенным выше кодом и функцией Thunk заключается в том, что за yield следуют функция Thunk и объект Promise.
Если вы понимаете самоисполнитель Thunk и Promise в порядке, вы можете понять этот код, взглянув на него, и нет никаких объяснений.
Далее давайте взглянем на исходный код библиотеки co.
Исходный код библиотеки функций CO также очень прост, всего несколько десятков строк кода.
Во-первых, функция co принимает в качестве параметра функцию Generator и возвращает объект Promise.
function co(gen) {
var ctx = this
return new Promise(function(resolve, reject) {
})
}
В возвращенном объекте Promise co сначала проверяет, является ли параметр gen функцией-генератором.
Если это так, выполните функцию и получите объект внутреннего указателя.
Если нет, вернитесь и измените состояние объекта Promise на разрешенное.
function co(gen) {
var ctx = this
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx)
if (!gen || typeof gen.next !== 'function') return resolve(gen)
})
}
Затем co оборачивает следующий метод объекта внутреннего указателя функции Generator в функцию onFulefilled.
В основном, чтобы иметь возможность ловить ошибки
function co(gen) {
var ctx = this
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx)
if (!gen || typeof gen.next !== 'function') return resolve(gen)
onFulfilled()
function onFulfilled(res) {
var ret
try {
ret = gen.next(res)
} catch (e) {
return reject(e)
}
next(ret)
}
})
}
Наконец, следующая важная функция, которая многократно вызывает сама себя
function next(ret) {
if (ret.done) return resolve(ret.value)
var value = toPromise.call(ctx, ret.value)
if (value && isPromise(value)) return value.then(onFulfilled, onRejected)
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, but the following object was passed: "' + String(ret.value) + '"'))
}
})
next
В методе первая строка проверяет, является ли текущий последним шагом функции Генератора, и возвращает, если он
Вторая строка гарантирует, что возвращаемое значение каждого шага является объектом Promise.
Третья строка, используйте метод then, добавьте функцию обратного вызова к возвращаемому значению, а затем снова вызовите следующую функцию через функцию onFulfilled
В четвертой строке при несоответствии параметров требованиям (параметры не являются функциями Thunk и объектами Promise) изменить состояние объекта Promise на reject, тем самым прервав выполнение
co поддерживает параллельные асинхронные операции, то есть позволяет выполнять определенные операции одновременно и ждет, пока они все не будут завершены, прежде чем перейти к следующему шагу.Мы можем размещать параллельные операции в массивах или объектах следующим образом.
// 数组的写法
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
]
console.log(res)
}).catch(onerror)
// 对象的写法
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
}
console.log(res)
}).catch(onerror)
-------👇-------
Выше приведено содержание co. Оно упоминается здесь только для того, чтобы каждый мог понять библиотеку функций co. Хотя в настоящее время она мало используется, нам полезно понять генератор. Даже если здесь это немного запутанно, это не вредно.знай что такое со,со Принцип автоматического исполнения наверное как его добиться
Как и Thunk, это также относится к статье г-на Руана Ифэна, поэтому, если вам интересно, вы можете прочитать ссылку [10]
Преимущества/недостатки генератора
преимущество
Элегантный метод управления процессом, который может прервать выполнение функции, что по-прежнему очень практично в некоторых особых случаях.
Студенты, которые использовали React-dva, могут чувствовать себя более эмоционально
До Node koa framework также использовал Generator, но позже он был заменен на async/await.
недостаток
Выполнение функции Generator должно полагаться на исполнителя, поэтому существует библиотека функций co, но согласно соглашению модуля co, за командой yield может следовать только функция Thunk или объект Promise, что не очень удобно для асинхронной обработки. Только.
Async/Await
Введение в асинхронность и ожидание
Представлен стандарт ES2017async
Функции, которые делают асинхронные операции более удобными
История решений для асинхронного программирования JS, от классических функций обратного вызова до мониторинга событий иPromise
, затем кGenerator
, а затем к тому, что мы собираемся сказатьAsync/Await
, это трудно
Async/Await
Появление JS многие считают окончательным и наиболее элегантным решением для асинхронных операций JS.
Async/Await
Мы все используем его много, и мы все знаем, что этоGenerator
синтаксический сахар для
На самом деле я думаюAsync/Await = Generator + Promise
Это объяснение больше подходит
async
является асинхронным иawait
даasync wait
сокращение для асинхронного ожидания
Так что семантически это легко понятьasync
используется для объявленияfunction
асинхронный,await
Используется для ожидания завершения выполнения асинхронного метода.
Кроме тогоawait
появляются только вasync
в функции
Пока что в чате, давайте кратко представим использование
что делает асинхронный
Давайте посмотрим на пример, чтобы понять
async function test() {
return "this is async"
}
const res = test()
console.log(res)
// Promise {<resolved>: "this is async"}
Как вы можете видеть, вывод представляет собой объект Promise.
так,async
Функция возвращает объект Promise, если вasync
Вернуть литерал прямо в функцию,async
пропустит это прямое количество черезPromIse.resolve()
Инкапсулируйте его как объект Promise и верните его
теперь, когдаasync
возвращает обещание, тогда мы также можем использоватьthen
цепочка для обработки этого объекта Promise следующим образом
test().then(res=>{
console.log(res)
})
что ждет
мы часто говоримawait
ожидает асинхронного завершения, на самом деле, согласно грамматике,await
Ожидание выражения, результатом которого является объект Promise или другое значение (другими словами, никаких особых ограничений, ничего)
-
await
Последний не является объектом Promise, выполняется напрямую -
await
За ним следует объект Promise, который заблокирует код позади объекта Promise.resolve
, а затем получитьresolve
значение, какawait
результат вычисления выражения -
await
только вasync
используется в функции
Он относительно прост в использовании, и все часто им пользуются, так что много говорить не буду.
Просто скажи, почемуawait
должен быть вasync
используется в функции
на самом деле это очень легко,await
Заблокирует код позади, если мы позволим нам использовать его напрямуюawait
, если мы используемawait
В ожидании асинхронного запроса, который занимает много времени, код будет заблокирован напрямую и не будет выполняться, а может только ждатьawait
Следующий код будет выполняться только после получения результата, поэтому он не будет перепутан.
а такжеasync
Вызов функции не вызовет блокировку, т.к. вся блокировка внутри нее инкапсулирована в объекте Promise для асинхронного выполнения, поэтому оговореноawait
Должен бытьasync
в функции
Обработка исключений
Промис разрешается нормально, затем await вернет этот результат, но в случае отклонения выдаст ошибку
Поэтому мы прямо ставимawait
блок кода, записанный вtry()catch()
Отловить ошибки в
async function fn(){
try{
let res = await ajax()
console.log(res)
}catch(err){
console.log(err)
}
}
нет сравнения нет вреда
Мы часто сталкиваемся с таким бизнесом, множественными запросами, каждый запрос зависит от результата предыдущего запроса
Мы используем setTimeout для имитации асинхронных операций и используем Promise и Async/Await для достижения следующих результатов соответственно.
function analogAsync(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 500), n)
})
}
function fn1(n) {
console.log(`step1 with ${n}`)
return analogAsync(n)
}
function fn2(n) {
console.log(`step2 with ${n}`)
return analogAsync(n)
}
function fn3(n) {
console.log(`step3 with ${n}`)
return analogAsync(n)
}
Использование промисов
function fn(){
let time1 = 0
fn1(time1)
.then((time2) => fn2(time2))
.then((time3) => fn3(time3))  
.then((res) => {
console.log(`result is ${res}`)
})
}
fn()
Использовать асинхронный режим/ожидание
async function fn() {
let time1 = 0
let time2 = await fn1(time1)
let time3 = await fn2(time2)
let res = await fn3(time3)
console.log(`result is ${res}`)
}
fn()
Вывод такой же, как и у реализации Promise выше, но этоaaync/await
Структура кода выглядит намного понятнее, почти как при синхронном написании, очень элегантно
Давайте посмотрим на следующий небольшой пример
// Generator
function* gen(){
let f1 = yield ajax()
let f2 = yield ajax()
}
gen()
// async/await
async function asyncAjax(){
let f1 = await ajax()
let f2 = await ajax()
}
asyncAjax()
Эти два фрагмента кода выглядят почти одинаково?
Приведенная выше функция выполняет два ajax для функции Generator, а следующая функция выполняется для async/await.
Можно обнаружить, что две функции на самом деле одинаковы,async
Но генераторная функция*
Замените число наasync
,yield
заменитьawait
Затем, когда эти две функции вызываются, функцию генератора необходимо вызывать вручную.next
метод или используйте библиотеку функций co для выполнения, и следующиеasync
Функции выполняются прямо по порядку, что очень удобно в использовании
Цель асинхронного программирования состоит в том, чтобы сделать его более похожим на синхронное программирование.Async/Await
прекрасно иллюстрирует это
Здесь мы можем видеть этоAsync/Await
Ребенок закончилGenerator
а такжеPromise
Преимущество/недостаток Async/Await
преимущество
Встроенный исполнитель, выполнение функции генератора должно полагаться на исполнителя, поэтому существует библиотека дополнительных функций, иasync
У функций есть свои исполнители, т.async
Выполнение функции точно такое же, как и у обычной функции, только одна строка
лучшая семантика,async
а такжеawait
, в сравнении с*
а такжеyield
, семантика яснее,async
Указывает, что в функции есть асинхронная операция,await
Указывает, что следующее выражение должно дождаться результата
более широкая применимость, соглашения о совместной библиотеке,yield
За командами могут следовать только функции Thunk или объекты Promise, иasync
функциональныйawait
После команды за ней могут следовать объекты Promise и значения примитивных типов (числа, строки и логические значения, но это эквивалентно синхронной операции)
недостаток
злоупотреблятьawait
может вызвать проблемы с производительностью, потому чтоawait
Это заблокирует код.Возможно, асинхронный код после этого не зависит от первого, но ему все равно нужно дождаться завершения первого, что приведет к потере параллелизма кода.
Сравнение асинхронных решений
Не смотри, я не резюмировал сравнение
На самом деле, условно говоря, написано очень подробно, а то, что вы можете рассказать, это ваше собственное.Вы можете сделать сравнение или резюме, исходя из преимуществ и недостатков, перечисленных в каждом плане, и собственного понимания.В конце концов, вы Я видел это.Вот, я не думаю, что нужно так долго читать сухие посты в 20 000 слов.В конце концов, я должен что-то приобрести.
напиши в конце
Уровень ограничен, добро пожаловать на ошибки
Кодировать слова непросто, у каждого есть что выиграть, не забывайте ставить лайки и поощрять
Эта статья объединяет предыдущую статью«Хардкорный JS» понимает механизм работы JS одновременноЧасть чтения, будет иметь более глубокое понимание асинхронного программирования JS, рекомендуемое чтение
Найдите [Irregular front-end] или отсканируйте код напрямую, чтобы подписаться на официальный аккаунт, чтобы увидеть больше интересных статей, а также несколько бесплатных обучающих видео, ресурсов и других товаров первой необходимости, предоставленных друзьями группы.
Вы также можете напрямую добавить меня в WeChat, войти в группу обмена, чтобы учиться и обмениваться
Ссылаться на
- 4 метода асинхронного программирования на Javascript
- Promise-MDN
- iterable-Ляо Сюэфэн
- What is Promise.try, and why does it matter?
- Что такое Promise.try и почему это так важно? -Ссылка 4 перевода
- Спецификация Promise/A+ - английский оригинал
- Спецификация Promise/A+ — перевод на китайский язык
- Значение и использование функции генератора - Жуань Ифэн
- Значение и использование функции Thunk - Жуан Ифэн
- Значение и использование совместной библиотеки - Руан Ифэн