Принцип асинхронности/ожидания и анализ последовательности выполнения

внешний интерфейс
Принцип асинхронности/ожидания и анализ последовательности выполнения

Написано раньше«На этот раз досконально поймите принцип Обещания»Пациент из принципа Обещания, реакция хорошая, и на этот раз также записываются знания, связанные с исследованием.

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

По этой причине в ES7 появился async/await, который является значительным улучшением асинхронного программирования на JavaScript.Он дает возможность использовать синхронный код для асинхронного доступа к ресурсам без блокировки основного потока и делает логику кода более понятной.Поддержка try-catch to ловить исключения, что очень соответствует человеческому линейному мышлению.

Итак, посмотрите, как реализовать async/await. В общем, async является синтаксическим сахаром для функции Generator и улучшает функцию Generator.

Введение в функции генератора

Функция Generator — это конечный автомат, который инкапсулирует несколько внутренних состояний. Выполнение функции Generator вернет объект обхода, который может по очереди проходить каждое состояние внутри функции Generator, но только путем вызоваnextМетод будет проходить через следующее внутреннее состояние, поэтому он фактически предоставляет функцию, которая может приостановить выполнение.yieldВыражение является флагом паузы.

Есть такой кусок кода:

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

Звоните и запускайте результаты:

hw.next()// { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next()// { value: undefined, done: true }

Из результатов видно, чтоGenerator函数не выполняется при вызове, только при вызовеnext方法, оператор будет выполнен только тогда, когда внутренний указатель указывает на оператор,即函数可以暂停,也可以恢复执行. Каждый раз, когда вызывается следующий метод объекта итератора, он возвращаетvalueа такжеdoneОбъект с двумя свойствами.valueАтрибут представляет значение текущего внутреннего состояния, которое является значением выражения, следующего за выражением yield;doneСвойство является логическим значением, указывающим, завершен ли обход.

Принцип паузы и возобновления работы генератора

Чтобы понять, почему функции могут быть приостановлены и возобновлены, вы должны сначала понять концепцию сопрограмм.

Когда поток (или функция) выполнен наполовину, он может приостановить выполнение, передать право на выполнение другому потоку (или функции) и возобновить выполнение, когда право на выполнение будет восстановлено позже. Этот поток (или функция), который может выполняться параллельно и обмениваться правами на выполнение, называется сопрограммой.

Coroutine является более легким, чем существуют потоки. Общая нить является упреждающим, будет конкурировать за ресурсы ЦП, и COROUTINE - это сотрудничество, CO-процедуры могут быть запущены в потоке в качестве задачи, могут быть несколько COROUTINES на потоке, но только в то время как на потоке выполнение COROUTINE. Процесс его работы выглядит следующим образом:

  1. сопрограммаAНачать выполнение
  2. сопрограммаAВыполнить до определенного этапа, ввести приостановку и передать право выполнения сопрограммеB
  3. сопрограммаBВыполнение завершается или приостанавливается, и право на выполнение возвращаетсяA
  4. сопрограммаAвозобновить выполнение

Сопрограммы встречаютсяyield命令Просто приостановите, подождите, пока право на выполнение вернется, а затем возобновите выполнение с того места, где оно было приостановлено. Его самое большое преимущество в том, что код написан очень похоже на синхронную операцию, если убрать команду yield, то все точно так же.

Актуатор

Обычно мы инкапсулируем код, выполняющий генератор, в функцию, а функцию, выполняющую код генератора, называем исполнителем.co 模块является известным исполнителем.

Генератор — это контейнер для асинхронных операций. Для его автоматического выполнения требуется механизм, который может автоматически возвращать право на выполнение, когда асинхронная операция имеет результат. Два способа сделать это:

  1. Перезвоните. Оберните асинхронную работу в функцию Thunk и верните выполнение прямо в функции обратного вызова.
  2. Обещанный объект. Оберните асинхронную операцию в объект Promise и верните право на выполнение с помощью метода then.

Простой автоматический исполнитель на основе объектов Promise:

function run(gen){
  var g = gen();

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

Когда мы используем его, мы можем использовать его так,


function* foo() {
    let response1 = yield fetch('https://xxx') //返回promise对象
    console.log('response1')
    console.log(response1)
    let response2 = yield fetch('https://xxx') //返回promise对象
    console.log('response2')
    console.log(response2)
}
run(foo);

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

async/await

ES7 представил async/await, который может полностью попрощаться с исполнителями и генераторами и получить более интуитивно понятный и лаконичный код. Согласно определению MDN, async — это функция, которая выполняется асинхронно и в результате неявно возвращает промис. Можно сказать, что асинхронность является синтаксическим сахаром функции Генератора и улучшает функцию Генератора.

В приведенном выше коде используйтеasyncДля достижения этой цели:

const foo = async () => {
    let response1 = await fetch('https://xxx') 
    console.log('response1')
    console.log(response1)
    let response2 = await fetch('https://xxx') 
    console.log('response2')
    console.log(response2)
}

После сравнения вы обнаружите, что асинхронная функция заменяет звездочку (*) функции генератора на асинхронную, а доходность заменяет на ожидание, вот и все.

Улучшение ASYNC-функции к функции генератора отражена в следующих четырех пунктах:

  1. 内置执行器. Выполнение функции Generator должно зависеть от исполнителя, а у асинхронной функции есть собственный исполнитель, поэтому нет необходимости вручную выполнять метод next().
  2. 更好的语义. async и await имеют более четкую семантику, чем asterisk и yield. async означает, что в функции есть асинхронная операция, а await означает, что следующему выражению необходимо дождаться результата.
  3. 更广的适用性. Согласно соглашению модуля co, за командой yield может следовать только функция Thunk или объект Promise, в то время как за командой await в асинхронной функции может следовать объект Promise и значения примитивного типа (числовые, строковые и логические). значений, но он будет автоматически преобразован в немедленно разрешенный объект Promise).
  4. 返回值是 Promise. Возвращаемое значение асинхронной функции — это объект Promise, который более удобен, чем объект Iterator, возвращаемый функцией Generator, и может быть вызван непосредственно с помощью метода then().

Дело тут в том, что с ним идет экзекьютор, что равносильно инкапсуляции всего, что нам нужно делать дополнительно (написание экзекьютора/в зависимости от модуля co). Например:

async function fn(args) {
  // ...
}

Эквивалентно:

function fn(args) {
  return spawn(function* () {
    // ...
  });
}

function spawn(genF) { //spawn函数就是自动执行器,跟简单版的思路是一样的,多了Promise和容错处理
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

асинхронный/ожидающий порядок выполнения

Из приведенного выше анализа мы знаем, чтоasyncФункция, которая неявно возвращает Promise в качестве результата, может быть просто понята так: когда функция, следующая за await, выполняется, await генерирует микрозадачу (Promise.then — это микрозадача). Но стоит обратить внимание на тайминг этой микрозадачи, после выполнения await она выскакивает из асинхронной функции и выполняет другой код (вот работа сопрограммы, А приостанавливает выполнение, а управление передается Б ). После выполнения другого кода вернитесь к асинхронной функции, чтобы выполнить остальную часть кода, а затем зарегистрируйте код ожидания в очереди микрозадач. Давайте посмотрим на пример:

console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

Проанализируйте этот код:

  • выполнить код, вывестиscript start.
  • Выполните async1(), вызовите async2() и затем выведитеasync2 end, В это время контекст функции async1 будет сохранен, а затем функция async1 будет выскочить.
  • Когда встречается setTimeout, создается задача макроса
  • Выполнить обещание, выводPromise. Затем столкнитесь, сгенерируйте первую микрозадачу
  • Продолжайте выполнять код, выводscript end
  • Выполняется логика кода (выполняется текущая задача макроса), запуская очередь микросмысла, сгенерированную текущей задачей макроса, выводpromise1, затем встречается микрозадача и генерируется новая микрозадача
  • Выполнить полученную микрозадачу, выводpromise2, выполняется текущая очередь микрозадач. Выполнение обратно в async1
  • Выполнение await фактически сгенерирует возврат обещания, т.е.
let promise_ = new Promise((resolve,reject){ resolve(undefined)})

Когда выполнение будет завершено, выполните оператор, следующий за ожиданием и выводомasync1 end

  • Наконец, выполните следующую задачу макроса, то есть выполните setTimeout, выводsetTimeout

Уведомление

Новая версия браузера Chrome не печатает, как указано выше, потому что Chrome оптимизирован, ожидание становится быстрее, и вывод:

// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

Но такая практика на самом деле противоречит норме, и норму, конечно, можно изменить, это часть команды V8.PR, текущая версия печати изменена. Есть также связанные обсуждения на Zhihu, вы можете взглянутьУуху. Call.com/question/26…

Мы можем понять это в двух случаях:

  1. Если за ожиданием непосредственно следует переменная, например: Затем выйдите из функции async1 и выполните другие коды.Когда встречается функция обещания, функция promise.then() будет зарегистрирована в очереди микрозадач.Обратите внимание, что микрозадача, стоящая за ожиданием, уже существует в очереди микрозадач в это время. Таким образом, в этом случае сначала выполняется код после ожидания (окончание async1), а затем код микрозадачи (обещание1, обещание2), зарегистрированный после выполнения функции async1.

  2. Если за ожиданием следует вызов асинхронной функции, такой как приведенный выше код, измените код на этот:

console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
  console.log('async2 end1')
})
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')

Результат:

// script start => async2 end => Promise => script end =>async2 end1 => promise1 => promise2 => async1 end => setTimeout

В это время после выполнения await код после await сначала не прописывается в очередь микрозадачи, а после выполнения await сразу выскакивает функция async1 и выполняются другие коды. Затем, когда вы встретите промис, зарегистрируйте promise.then как микрозадачу. После выполнения другого кода необходимо вернуться к функции async1 для выполнения оставшегося кода, а затем зарегистрировать код после ожидания в очереди микрозадач.Обратите внимание, что в это время в очереди микрозадач уже есть ранее зарегистрированные микрозадачи. Таким образом, в этом случае сначала будут выполняться микрозадачи (обещание1, обещание2) вне функции async1, а затем будут выполняться микрозадачи, зарегистрированные в async1 (окончание async1).

использованная литература

Обещать данные

наконец

  • Добро пожаловать, чтобы добавить меня в WeChat (winty230), привлечь вас в техническую группу, долгосрочный обмен и обучение...
  • Добро пожаловать, чтобы обратить внимание на «Front-end Q», серьезно изучить интерфейс и быть профессиональным техническим специалистом...

GitHub