«Hardcore JS» погружается в асинхронные решения

JavaScript
«Hardcore JS» погружается в асинхронные решения

предисловие

Среда выполнения языка 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Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.thenonRejected

thenВторой параметр метода onRejected заключается в том, что ток нельзя контролироватьthenЕсли обратный вызов метода ненормальный, спецификация определяет текущийthenЕсли в методе есть исключение, вызывается метод в состоянии failed (reject) и поток переходит к следующемуthenonRejected

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принадлежатьiterableType , об 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 — микрозадача, а порядок выполнения разный.

Таким образом, порядок выполнения этого кода:

  1. Стек выполнения кода входит в обещание, запускает setTimeOut, а функция обратного вызова setTimeOut входит в очередь задач макроса.
  2. Код выполняет метод catch обещания и попадает в очередь микрозадач, в это время обратный вызов setTimeOut еще не выполнен.
  3. Проверка стека выполнения обнаруживает, что текущая очередь микрозадач выполняется, и начинает выполняться очередь макрозадач.
  4. воплощать в жизнь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Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.thenonRejected

  • если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 строк кода.

Обещай хорошо/плохо

преимущество

Промисы пишут асинхронный код синхронно, избегая уровней вложенных функций обратного вызова.

Объекты 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..ofLoop, интерфейс 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метод
    • Вызов функции-генератора аналогичен вызову обычной функции, просто добавьте () после имени функции.
    • Функции-генераторы не выполняются немедленно, как обычные функции, а возвращают указатель на объект внутреннего состояния.
    • Итак, чтобы вызвать объект итератора Iteratornextметод указатель начнет выполнение с начала функции или с того места, где он остановился
    • 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))&emsp;&emsp;
    .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, войти в группу обмена, чтобы учиться и обмениваться

Ссылаться на

  1. 4 метода асинхронного программирования на Javascript
  2. Promise-MDN
  3. iterable-Ляо Сюэфэн
  4. What is Promise.try, and why does it matter?
  5. Что такое Promise.try и почему это так важно? -Ссылка 4 перевода
  6. Спецификация Promise/A+ - английский оригинал
  7. Спецификация Promise/A+ — перевод на китайский язык
  8. Значение и использование функции генератора - Жуань Ифэн
  9. Значение и использование функции Thunk - Жуан Ифэн
  10. Значение и использование совместной библиотеки - Руан Ифэн