Углубленный анализ асинхронных функций ES2017.

внешний интерфейс ECMAScript 6

Это первый день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления

асинхронная функция

имея в виду

Стандарт ES2017 представил асинхронные функции, чтобы сделать асинхронные операции более удобными.

Что такое асинхронная функция? Одним словом, это синтаксический сахар для Генераторных функций.

В предыдущей статье была функция Generator, которая по очереди считывает два файла.

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

функция кода вышеgenможно записать какasyncфункционируют следующим образом.

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

Сравнение показывает, что,asyncФункция является звездочкой Генераторной функции (*) заменяется наasync,Будуyieldзаменитьawait, это все.

asyncУлучшение функции Генератора отражено в следующих четырех пунктах.

(1) Встроенный привод.

Выполнение функции генератора должно зависеть от исполнителя, поэтомуcoмодуль, покаasyncУ функций есть свои исполнители. То есть,asyncВыполнение функции точно такое же, как и у обычной функции, требуется только одна строка.

asyncReadFile();

Приведенный выше код вызываетasyncReadFileфункция, то она автоматически выполнится и выведет окончательный результат. Это совсем не похоже на Генераторную функцию, которую нужно вызыватьnextметод или использованиеcoМодуль можно реально выполнить и получить окончательный результат.

(2) Лучшая семантика.

asyncа такжеawait, по сравнению со звездочками иyield, семантика яснее.asyncУказывает, что в функции есть асинхронная операция,awaitУказывает, что следующее выражение должно ожидать результата.

(3) Более широкая применимость.

coсоглашения модулей,yieldЗа командами могут следовать только функции Thunk или объекты Promise, иasyncфункциональныйawaitПосле команды это может быть объект Promise и значение примитивного типа (числовое, строковое и логическое, но в этом случае оно будет автоматически преобразовано в сразу разрешенный объект Promise).

(4) Возвращаемое значение — Обещание.

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

Дальше,asyncФункцию можно рассматривать как несколько асинхронных операций, заключенных в объект Promise иawaitкоманда является внутреннейthenСинтаксический сахар для команд.

Основное использование

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

Ниже приведен пример.

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

Приведенный выше код представляет собой функцию для получения котировок акций, предыдущая функцияasyncключевое слово, указывающее, что внутри функции есть асинхронная операция. Когда функция вызывается, она немедленно возвращаетPromiseобъект.

Вот еще один пример, который указывает, через сколько миллисекунд выводить значение.

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

Приведенный выше код указывает, что через 50 миллисекунд выводhello world.

из-заasyncФункция возвращает объект Promise, который можно использовать какawaitПараметры команды. Таким образом, приведенный выше пример также можно записать в следующем виде.

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

Асинхронные функции могут использоваться в различных формах.

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

грамматика

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

Возвращает объект обещания

asyncФункция возвращает объект Promise.

asyncвнутри функцииreturnЗначение, возвращаемое оператором, станетthenПараметры функции обратного вызова метода.

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

В приведенном выше коде функцияfвнутреннийreturnЗначение, возвращаемое командой, будетthenПолучена функция обратного вызова метода.

asyncВнутри функции возникает ошибка, из-за которой возвращаемый объект Promise становитсяrejectусловие. Выброшенный объект ошибки будетcatchПолучена функция обратного вызова метода.

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了

Изменения состояния объектов Promise

asyncОбъект Promise, возвращаемый функцией, должен ждать, пока все внутренниеawaitИзменение состояния не произойдет до тех пор, пока объект Promise, следующий за командой, не будет выполнен, если только он не встретитreturnзаявление или выдать ошибку. То есть толькоasyncАсинхронная операция внутри функции будет выполняться после выполненияthenФункция обратного вызова, указанная методом.

Ниже приведен пример.

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

В приведенном выше коде функцияgetTitleЕсть три внутренних операции: захватить веб-страницу, извлечь текст и сопоставить заголовок страницы. Только после того, как все три операции будут завершены, он будет выполненthenв методеconsole.log.

команда ожидания

При нормальных обстоятельствах,awaitЗа командой следует объект Promise, который возвращает результат этого объекта. Если это не объект Promise, он напрямую возвращает соответствующее значение.

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

В приведенном выше кодеawaitАргументы команды являются числовыми значениями.123, что эквивалентноreturn 123.

В другом случаеawaitЗа командой следуетthenableобъект (то есть определяетthenобъект метода), затемawaitприравнивает его к объекту Promise.

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
// 1000

В приведенном выше кодеawaitЗа командой следуетSleepэкземпляр объекта. Этот экземпляр не является объектом Promise, но поскольку определениеthenметод,awaitотносился бы к этому как кPromiseиметь дело с.

В этом примере также показано, как реализовать эффект сна. В JavaScript никогда не было синтаксиса для перехода в спящий режим, но с помощьюawaitКоманда для приостановки работы программы на указанное время. упрощенныйsleepвыполнить.

function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  })
}

// 用法
async function one2FiveInAsync() {
  for(let i = 1; i <= 5; i++) {
    console.log(i);
    await sleep(1000);
  }
}

one2FiveInAsync();

awaitЕсли объект Promise после команды становитсяrejectстатус, тоrejectпараметры будутcatchФункция обратного вызова метода получает его.

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

Обратите внимание, что в приведенном выше кодеawaitнет предшествующего утвержденияreturn,ноrejectПараметры метода по-прежнему передаются вcatchфункция обратного вызова метода. здесь, если вawaitдобавленныйreturn, эффект тот же.

любойawaitОбъект Promise после оператора становитсяrejectсостояние, то весьasyncфункция прервет выполнение.

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

В приведенном выше коде второйawaitоператор не выполняется, потому что первыйawaitСостояние оператора становитсяreject.

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

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

Другой способawaitЗа следующим объектом Promise следуетcatchспособ устранения ошибок, которые могут возникнуть ранее.

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

обработка ошибок

еслиawaitПри последующей асинхронной операции возникает ошибка, тогда она эквивалентнаasyncОбъект Promise, возвращаемый функцией,reject.

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了

В приведенном выше кодеasyncфункцияfПосле казни,awaitСледующий объект Promise выдаст объект ошибки, в результате чегоcatchВызывается функция обратного вызова метода, и ее параметром является выброшенный объект ошибки. Конкретный механизм выполнения см. в разделе «Принцип реализации асинхронной функции» ниже.

Способ предотвращения ошибок также состоит в том, чтобы помещать их вtry...catchв кодовом блоке.

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

Если есть несколькоawaitкоманда, может быть помещена в единыйtry...catchв структуре.

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

В следующем примере используетсяtry...catchструктура для достижения нескольких повторных попыток.

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}

test();

В приведенном выше коде, еслиawaitЕсли операция прошла успешно, она будет использованаbreakоператор выходит из цикла; в случае неудачи он будетcatchЗахват оператора, а затем вход в следующий раунд цикла.

Примечания по использованию

Первый пункт, как уже упоминалось ранее,awaitпосле командыPromiseобъект, результат операции может бытьrejected, так что лучше всегоawaitкоманда наtry...catchв кодовом блоке.

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

Во-вторых, несколькоawaitАсинхронные операции, следующие за командой, если нет вторичной связи, лучше позволить им запускаться одновременно.

let foo = await getFoo();
let bar = await getBar();

В приведенном выше кодеgetFooа такжеgetBarдве независимые асинхронные операции (то есть независимые друг от друга), записанные как вторичная связь. Это занимает много времени, потому что толькоgetFooПосле завершения он будет выполненgetBar, вы можете заставить их срабатывать одновременно.

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

Вышеуказанные два способа написания,getFooа такжеgetBarВсе запускаются одновременно, что сокращает время выполнения программы.

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

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

Приведенный выше код сообщит об ошибке, потому чтоawaitиспользуется в обычных функциях. Однако, еслиforEachПараметры метода изменены наasyncфункции, есть и проблема.

function dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

Приведенный выше код может работать некорректно, причина в том, что триdb.post()Операции будут выполняться параллельно, то есть параллельно, а не последовательно. Правильное написание - использоватьforцикл.

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

Другой способ — использовать массивreduce()метод.

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  await docs.reduce(async (_, doc) => {
    await _;
    await db.post(doc);
  }, undefined);
}

В приведенном выше примереreduce()Первый параметр методаasyncфункция, которая вызывает первый параметр функции, чтобы быть объектом обещания, возвращаемой предыдущей операцией, поэтому вы должны использоватьawait等待它操作结束。 Кроме того,reduce()метод возвращаетdocsпоследний элемент массиваasyncРезультатом выполнения функции также является объект Promise, поэтому его тоже нужно добавить перед нимawait.

надreduce()В функции нет параметраreturnзаявление, причина в том, что основная цель этой функции состоит в том, чтобыdb.post()Операция, а не возвращаемое значение. а такжеasyncфункционировать с или безreturnвсегда возвращает объект Promise, поэтому здесьreturnне нужно.

Если вы действительно хотите, чтобы несколько запросов выполнялись одновременно, вы можете использоватьPromise.allметод. когда будет три запросаresolved, следующие два способа написания имеют одинаковый эффект.

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

В-четвертых, асинхронные функции могут сохранять стек выполнения.

const a = () => {
  b().then(() => c());
};

В приведенном выше коде функцияaАсинхронная задача выполняется внутриb(). когдаb()При работе функцияa()Не прерывает, а продолжает выполнение. Подожди покаb()конец пробега, возможноa()Давно пора,b()Контекст, в котором это было, исчез. еслиb()илиc()ошибка, стек ошибок не будет включатьa().

Теперь измените этот пример наasyncфункция.

const a = async () => {
  await b();
  c();
};

В приведенном выше кодеb()Во время бегаa()Есть приостановить выполнение, контекстная среда сохраняется. однаждыb()илиc()ошибка, стек ошибок будет включатьa().

Принцип реализации асинхронной функции

Принцип реализации асинхронной функции заключается в том, чтобы обернуть функцию-генератор и автоматический исполнитель в функцию.

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

// 等同于

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

всеasyncФункции могут быть записаны во второй форме выше, гдеspawnФункции являются автоматическими исполнителями.

нижеприведенныйspawnРеализация функции в основном является копией предыдущего автоматического исполнителя.

function spawn(genF) {
  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); });
  });
}

Сравнение с другими методами асинхронной обработки

Мы используем пример, посмотрим на асинхронную функцию и сравнение промисов, функцию генератора.

Предполагая, что в элементе DOM развернута серия анимаций, предыдущая анимация может быть запущена только после окончания предыдущей анимации. Если в одной из анимаций есть ошибка, она не будет выполнена, а будет возвращено возвращаемое значение последней успешно выполненной анимации.

Во-первых, это способ написания Promise.

function chainAnimationsPromise(elem, animations) {

  // 变量ret用来保存上一个动画的返回值
  let ret = null;

  // 新建一个空的Promise
  let p = Promise.resolve();

  // 使用then方法,添加所有动画
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一个部署了错误捕捉机制的Promise
  return p.catch(function(e) {
    /* 忽略错误,继续执行 */
  }).then(function() {
    return ret;
  });

}

Хотя способ написания Promise значительно улучшен по сравнению с callback-функцией, но на первый взгляд код полностью соответствует API Promise (then,catchд.), семантику самой операции увидеть непросто.

Далее следует способ написания функции генератора.

function chainAnimationsGenerator(elem, animations) {

  return spawn(function*() {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* 忽略错误,继续执行 */
    }
    return ret;
  });

}

Приведенный выше код использует функцию Generator для обхода каждой анимации.Семантика понятнее, чем у Promise, и все пользовательские операции появляются вspawnвнутри функции. Проблема с таким способом написания заключается в том, что должен быть обработчик задач, который автоматически выполняет функцию Генератора.spawnФункция является автоматическим исполнителем, она возвращает объект Promise и должна гарантироватьyieldВыражение, следующее за оператором, должно возвращать обещание.

Последний способ написать асинхронную функцию.

async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}

Видно, что реализация асинхронной функции самая лаконичная, самая семантически совместимая, и почти нет семантически нерелевантного кода. Он обеспечивает автоматический исполнитель в методе написания Генератора на уровне языка и не выставляет его пользователям, поэтому объем кода минимален. Если вы используете метод написания генератора, автоматический исполнитель должен быть предоставлен пользователем.

Пример: последовательное выполнение асинхронных операций

В реальной разработке часто встречается набор асинхронных операций, которые нужно выполнять последовательно. Например, удаленно прочитать набор URL-адресов по очереди, а затем вывести результаты в том порядке, в котором они были прочитаны.

Обещания пишутся следующим образом.

function logInOrder(urls) {
  // 远程读取所有URL
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // 按次序输出
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

Используйте приведенный выше кодfetchметод удаленного чтения набора URL-адресов одновременно. каждыйfetchОперации возвращают объект Promise, вставленныйtextPromisesмножество. Потом,reduceметод обрабатывает каждый объект Promise по очереди, а затем используетthen, который объединяет все объекты Promise, поэтому результаты можно вывести последовательно.

Такой способ написания менее интуитивен и менее удобочитаем. Ниже приведена реализация асинхронной функции.

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}

Код выше действительно сильно упрощен, проблема в том, что все удаленные операции второстепенны. Следующий URL-адрес будет прочитан только в том случае, если предыдущий URL-адрес возвращает результат, что неэффективно и является пустой тратой времени. Что нам нужно, так это делать удаленные запросы одновременно.

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

В приведенном выше коде, хотяmapПараметры методаasyncфункция, но она выполняется одновременно, потому что толькоasyncВнутренняя часть функции является вторичным выполнением, а внешняя не затрагивается. Назадfor..ofиспользуется внутри циклаawait, тем самым достигая последовательного вывода.

ожидание верхнего уровня

Согласно спецификации синтаксиса,awaitКоманды могут появляться только внутри асинхронных функций, иначе будет сообщено об ошибке.

// 报错
const data = await fetch('https://api.example.com');

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

В настоящее время существуетПредложение по грамматике, позволяющее независимое использование на верхнем уровне модуляawaitкоманда, чтобы приведенная выше строка кода не сообщала об ошибке. Цель этого предложения - заимствованиеawaitРешить проблему асинхронной загрузки модулей.

// awaiting.js
let output;
async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
}
main();
export { output };

В приведенном выше коде модульawaiting.jsвыходное значениеoutput, в зависимости от асинхронной операции. Мы оборачиваем асинхронную операцию в асинхронную функцию, а затем вызываем эту функцию, только после того, как асинхронная операция внутри будет выполнена, переменнаяoutputбудет иметь значение, иначе возвратundefined.

Приведенный выше код также можно записать как непосредственную функцию.

// awaiting.js
let output;
(async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
})();
export { output };

Ниже показано, как загрузить этот модуль.

// usage.js
import { output } from "./awaiting.js";

function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100)), 1000);

В приведенном выше кодеoutputPlusValue()Результат выполнения полностью зависит от времени выполнения. еслиawaiting.jsАсинхронная операция внутри не завершена, а загруженныйoutputЗначениеundefined.

Текущее решение состоит в том, чтобы позволить исходному модулю выводить объект Promise и судить, завершилась ли асинхронная операция из этого объекта Promise.

// awaiting.js
let output;
export default (async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
})();
export { output };

В приведенном выше кодеawaiting.jsкроме выводаoutput, а также по умолчанию выводит объект Promise (после выполнения асинхронной функции сразу же возвращает объект Promise), по которому судят о завершении асинхронной операции.

Ниже приведен новый способ загрузки этого модуля.

// usage.js
import promise, { output } from "./awaiting.js";

function outputPlusValue(value) { return output + value }

promise.then(() => {
  console.log(outputPlusValue(100));
  setTimeout(() => console.log(outputPlusValue(100)), 1000);
});

В приведенном выше кодеawaiting.jsвывод объекта, помещенного вpromise.then()Внутри это может гарантировать, что асинхронная операция будет завершена до чтенияoutput.

Этот способ написания более сложен и эквивалентен требованию, чтобы пользователь модуля соблюдал дополнительный протокол использования и использовал модуль особым образом. Как только вы забудете загрузить промисы и просто используете метод обычной загрузки, код, который зависит от этого модуля, может дать сбой. Более того, если вышеуказанноеusage.jsТакже есть внешний вывод, а это значит, что все модули в этой цепочке зависимостей должны быть загружены с помощью Promise.

высший уровеньawaitкоманда, чтобы решить эту проблему. Это гарантирует, что модуль не выведет значение, пока не завершится асинхронная операция.

// awaiting.js
const dynamic = import(someMission);
const data = fetch(url);
export const output = someProcess((await dynamic).default, await data);

В приведенном выше коде две асинхронные операции добавляются при выводеawaitЗаказ. Этот модуль будет выводить значение только до завершения асинхронной операции.

Способ загрузки этого модуля следующий.

// usage.js
import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100)), 1000);

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

В это время загрузка модуля будет ожидать зависимого модуля (приведенный выше примерawaiting.js) асинхронной операции завершается до выполнения кода позади, что-то вроде паузы. Таким образом, это всегда будет правильноoutputНе одинаковое значение получается от разного времени загрузки тайминга.

Обратите внимание, что верхawaitМожет использоваться только в модулях ES6, но не в модулях CommonJS. Это связано с тем, что модуль CommonJSrequire()загружается синхронно, если есть верхний уровеньawait, он не может обрабатывать загрузку.

Ниже находится верхний слойawaitнесколько сценариев использования.

// import() 方法加载
const strings = await import(`/i18n/${navigator.language}`);

// 数据库操作
const connection = await dbConnector();

// 依赖回滚
let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}

Обратите внимание, что если загрузка нескольких файлов включает верхний уровеньawaitМодуль команды, команда загрузки выполняется синхронно.

// x.js
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");

// y.js
console.log("Y");

// z.js
import "./x.js";
import "./y.js";
console.log("Z");

Приведенный выше код состоит из трех модулей, последнийz.jsнагрузкаx.jsа такжеy.js, результат печатиX1,Y,X2,Z. это означает,z.jsне дождалсяx.jsЗагрузка завершена, перейдите к загрузке еще разy.js.

высший уровеньawaitКоманда немного похожа на передачу права на выполнение кода другим модулям для загрузки, а после завершения асинхронной операции вернуть право на выполнение и продолжить выполнение вниз.