- последнее обновление: 2020-12-14
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
Прежде всего, если вы хотите лучше понять Async/Await, вам нужно понять эти два момента:
- Синхронизировать
- асинхронный
задний план
первый,js однопоточный (повторить три раза), так называемые однопоточные,
С точки зрения непрофессионала,ребро(Аналогия слишком длинная, ха-ха) Исполняющий код идет вниз строка за строкой (так называемыйСинхронизировать),
Если вышеперечисленное не закончено, просто безумно ждите (это похоже на вас, которые ждут ее / его на обочине в любви, притворяясь, что у вас есть новый объект, ахахаха, это очень приятно быть непослушным),
Позвольте мне дать вам 🌰:
// chrome 81
function test() {
let d = Date.now();
for (let i = 0; i < 1e8; i++) {}
console.log(Date.now() - d); // 62ms-90ms左右
}
function test1() {
let d = Date.now();
console.log(Date.now() - d); // 0
}
test();
test1();
Это всего лишь цикл for, но в практических приложениях будет большое количество сетевых запросов, и время его ответа неизвестно, в таком случае нужно ли его ждать? Очевидно, что это невозможно, поэтому js спроектирован так, чтобы быть асинхронным, то есть инициировать сетевые запросы (такие как операции ввода-вывода, таймеры), потому что ему нужно ждать ответа сервера, он будет его игнорировать, но делать другие вещи. , и дождитесь, пока запрос вернет результат, когда сказать (т.е.асинхронный).
Так как же добиться асинхронности? На самом деле, мы часто использовали его в обычное время, т.е.callback,Например:
// 网络请求
$.ajax({
url: 'http://xxx',
success: function(res) {
console.log(res);
},
});
Успех, переданный как функция, не будет выполнен сразу, а будет выполнен после успешного выполнения запроса, т.е.Перезвоните(Перезвоните)
// IO操作
const fs = require('fs');
fs.rename('旧文件.txt', '新文件.txt', err => {
if (err) throw err;
console.log('重命名完成');
});
Подобно сетевому запросу, третий параметр не выполняется до тех пор, пока операция ввода-вывода не получит результат (успешный или нет):(err)=>{}
Из вышеперечисленного мы можем видеть, что ядро реализации Asynchrony является крючком обратного вызова, проходящего Cb в качестве параметра к функции асинхронного исполнения и запускается CB при получении результата. Чтобы узнать больше, перейти кevent-loopмеханизм.
Что касается появления async/await, то до es6 большинство js-проектов имели такой код:
ajax1(url, () => {
// do something 1
ajax2(url, () => {
// do something 2
ajax3(url, () => {
// do something 3
// ...
});
});
});
Такое вложение функций и большое количество функций обратного вызова делают код непонятным, трудным для понимания и неинтуитивным.ад обратного звонка, так чтобы популярнее писать, es6+ появлялись один за другимPromise,Generator,Async/await, а также стремиться быть кратким и ясным в написании (сглаживание) и легко читаемым (более элегантным и лаконичным).
========================== Я разделительная линия =================== == =====
Вышесказанное лишь предвестие, давайте перейдем к теме 👇 и начнем говорить о главном герое:async/await
========================== Я разделительная линия =================== == =====
async/awaitявляется ссылкойGeneratorИнкапсулированный набор решений для асинхронной обработки, который можно понимать какGeneratorсинтаксический сахар для ,
так поймиasync/awaitя должен поговоритьGenerator(Впервые представил концепцию сопрограммыjs, является подмножеством сопрограммы, но поскольку сопрограмма, которая уступает, не может быть указана, она может уступать только вызывающей стороне генератора (итератору), поэтому ее также называют асимметричной сопрограммой),
а такжеGeneratorвозвращаемый итераторIteratorобъект,
Итак, давайте поговоримIterator,
а такжеIteratorа такжеGeneratorВсе принадлежит сопрограмме,
Наконец нашел источник: сопрограммы
сопрограмма
вики:сопрограмма(англ. coroutine) — класс компонентов компьютерной программы, который обобщает совместную многозадачность подпрограмм, позволяя приостанавливать и возобновлять выполнение. Сопрограммы более общие и гибкие, чем подпрограммы, но на практике используются не так широко, как подпрограммы. Сопрограммы больше подходят для реализации знакомых компонентов программы, таких как совместная многозадачность, обработка исключений, циклы событий, итераторы, бесконечные списки и конвейеры.
Сопрограммы могут вызывать другие сопрограммы через yield (в зависимости от того, что означает «уступка», а не «производство»), и каждый раз, когда вызывается следующая сопрограмма, она выполняется с позиции, возвращенной последним выходом сопрограммы, через yield. caller и callee не находятся между сопрограммами, передающими права на выполнение, а симметричны и равны друг другу.
Корутины — это результат стремления к максимальной производительности и красивой структуре кода. Вызовы между сопрограммами логически управляемы и определяются последовательно во времени.
Сопрограмма представляет собой более легковесное существо, чем поток.Это конструкция на уровне языка, которую можно рассматривать как формупоток управления, выполняется в памяти без накладных расходов на переключение между потоками. Вы можете думать о сопрограммах как о задачах, выполняемых в потоках.В потоке может быть несколько сопрограмм, но одновременно в потоке может выполняться только одна сопрограмма.
Концепция сопрограммы была выдвинута ранее, и эта концепция развивалась в сценарии с одноядерным процессором, предоставляявешатьа такжевосстанавливатьсяИнтерфейс, который реализует параллельную функцию чередующейся обработки нескольких задач на одном процессоре.
Так что по сути на основе потока добавляется переключение разных стеков задач, а фрагменты кода, которые поочередно выполняются в потоке посредством приостановки и возобновления разных стеков задач, реализуют функцию параллелизма.
На самом деле из этого видно, что "вызов между сопрограммами логически управляем и время определяется"
Так как понять JS Coroutine это?
- js дорога — это только улица с односторонним движением (основной поток), но есть много полос (вспомогательный поток), которые можно объединить в поток трафика (после завершения асинхронной задачи callback попадает в очередь задач основного потока)
-
generatorПревратите js-дорогу в многополосную (реализация сопрограммы), но при этом может двигаться только одна полоса автомобиля (по-прежнему однопоточная), но может свободно менять полосы (управление хендовером)
Реализация сопрограммы
Вот простой пример, демонстрирующий полезность сопрограмм. Предполагая такие отношения производитель-потребитель, одна сопрограмма производит продукты и ставит их в очередь, а другая сопрограмма берет продукты из очереди и потребляет их. Псевдокод выглядит следующим образом:
var q := 新建队列
coroutine 生产者
loop
while q 不满载
建立某些新产品
向 q 增加这些产品
yield 给消费者
coroutine 消费者
loop
while q 不空载
从 q 移除某些产品
使用这些产品
yield 给生产者
Исходный код реализации V8:js-generator,runtime-generator
Скомпилируйте фиктивную реализацию (es5):regenerator
Через вышесказанное я делаю вид, что вы понимаете, что такое сопрограмма, и следующий шаг начинает говорить об этом.итератор Iterator
Iterator
IteratorПеревод **итератор (обходчик)**. Давайте посмотрим на процесс его обхода (аналогично односвязному списку):
- Создаватьобъект указателя, указывающий на начало текущей структуры данных
- Первый вызов объекта указателя
nextметод, который указывает указатель на первый член структуры данных - Второй вызов объекта указателя
nextметод, который указывает указатель на второй член структуры данных - Постоянный вызов объекта указателя
nextметод, пока он не укажет на конец структуры данных
Чтобы объект был итерируемым, он должен реализовать@@iteratorМетод, то есть объект (или какой-то объект на его цепочке прототипа) должен иметь имя, котороеSymbol.iteratorСвойства (изначально имеют это свойство):String,Array,TypedArray,Mapа такжеSet) можно получить через константуSymbol.iteratorдоступ:
| Атрибуты | стоимость |
|---|---|
| [Symbol.iterator]: | Функция без параметров, которая возвращает объект, возвращаемый объект соответствует протоколу итератора. |
Когда объект должен быть повторен (например, запуск дляfor..ofпетля), это@@iteratorМетод вызывается без аргументов, затем возвращает итератор для получения значения в итерации.
Протокол итератора: выдает конечную или бесконечную последовательность значений, и когда все значения повторяются, возвращается значение по умолчанию.
Объект считается итератором, только если он удовлетворяет следующим условиям:
он реализуетnext()метод, методдолжен вернуть объект, объект имеет два необходимых свойства:
-
done(логический)- true: Итератор превысил количество итераций. В этом случае значение value можно опустить.
- false, если итератор может дать следующее значение в последовательности. Это эквивалентно не указанию свойства done
-
valueЛюбое значение JavaScript, возвращаемое итератором. Может быть опущен, если done равно true
В соответствии с приведенными выше правилами настроим простой итератор:
const getRawType = (target) => Object.prototype.toString.call(target).slice(8,-1);
const __createArrayIterable = (arr) => {
if (typeof Symbol !== 'function' || !Symbol.iterator) return {};
if(getRawType(arr) !== 'Array') throw new Error('it must be Array');
const iterable = {};
iterable[Symbol.iterator] = () => {
arr.length++;
const iterator = {
next: () => ({ value: arr.shift(), done: arr.length <= 0 })
}
return iterator;
};
return iterable;
};
const itable = __createArrayIterable(['人月', '神话']);
const it = itable[Symbol.iterator]();
console.log(it.next()); // { value: "人月", done: false }
console.log(it.next()); // { value: "神话", done: false }
console.log(it.next()); // {value: undefined, done: true }
Мы также можем настроить итерируемый объект:
Object.prototype[Symbol.iterator] = function () {
const items = Object.entries(this);
items.length++;
return {
next: () => ({ value: items.shift(), done: items.length <= 0 })
}
}
// or
Object.prototype[Symbol.iterator] = function* () {
const items = Object.entries(this);
for (const item of items) {
yield item;
}
}
const obj = { name: 'amap', bu: 'sharetrip'}
for (let value of obj) {
console.log(value);
}
// ["name", "amap"]
// ["bu", "sharetrip"]
// or
console.log([...obj]); // [["name", "amap"], ["bu", "sharetrip"]]
💡 Как обойти массив кроме map forEach и т.д.?
справочный ответ
const getIterator = (iteratorable) => iteratorable[Symbol.iterator]();
const arr = [0,1,2,3,4,5];
const iterator = getIterator(arr);
while(true){
const obj = iterator.next();
if(obj.done){
break;
}
console.log(obj.value);
}
Зная об итераторах, давайте узнаем больше о генераторах
Generator
Generator: объект генератора возвращается функцией генератора (GeneratorFunction), которая соответствуетИтерируемый протокола такжепротокол итератора, который одновременно является итератором и итерируемым объектом, может быть вызванnextметод, но это не функция, а тем более конструктор
Генераторная функция (GeneratorFunction):
function* name([param[, param[, ... param]]]) { statements }
- имя: имя функции
- параметр: параметр
- операторы: js-операторы
Вызов функции-генератора не приводит к немедленному выполнению оператора в ней, а возвращает объект-итератор для генератора, когда итераторnext()Когда метод вызывается для первого (последующего) вызова, операторы внутри него выполняются до первого (последующего) появленияyieldположение (пусть исполнение будетприостановленный, зависает),yieldза которым следует значение, которое должно быть возвращено итератором. или при использованииyield*(со звездочкой), это означает, что выполнение передается другой функции генератора (текущему генераторуПриостановить выполнение),передачаnext()(перезагрузка, пробуждение), если передается параметр, то этот параметр будет использоваться какпоследний казненныйyieldвозвращаемое значение инструкции,Например:
function* another() {
yield '人月神话';
}
function* gen() {
yield* another(); // 移交执行权
const a = yield 'hello';
const b = yield a; // a='world' 是 next('world') 传参赋值给了上一个 yidle 'hello' 的左值
yield b; // b=! 是 next('!') 传参赋值给了上一个 yidle a 的左值
}
const g = gen();
g.next(); // {value: "人月神话", done: false}
g.next(); // {value: "hello", done: false}
g.next('world'); // {value: "world", done: false} 将 'world' 赋给上一条 yield 'hello' 的左值,即执行 a='world',
g.next('!'); // {value: "!", done: false} 将 '!' 赋给上一条 yield a 的左值,即执行 b='!',返回 b
g.next(); // {value: undefined, done: false}
Увидев это, вы можете спросить,Generatorа такжеcallbackИмеет ли значение, как бороться с асинхронностью? На самом деле, эти двое не имеют ничего общего друг с другом, мы просто принуждаем их к каким-то отношениям.Generatorобрабатывать асинхронность
Подведем итогиGeneratorСуть паузы в том, что она позволит программе выполниться до указанной позиции первой паузы (yield), затем начать (next), затем пауза (yield), начать сначала (next), и эту паузу легко связать с асинхронными операциями, потому что когда мы имеем дело с асинхронностью: запускаем асинхронную обработку (сетевое заступничество, IO-операцию), потом делаем паузу на время, а потом делаем, что делать, когда обработка закончена . Однако стоит отметить, чтоjs является однопоточным (повторяется три раза), асинхронный или асинхронный, обратный вызов или обратный вызов, а не потому, чтоGeneratorи любые изменения
Давайте посмотрим ниже, используяGenerator + PromiseНапишите кусок асинхронного кода:
const gen = function*() {
const res1 = yield Promise.resolve({a: 1});
const res2 = yield Promise.resolve({b: 2});
};
const g = gen();
const g1 = g.next();
console.log('g1:', g1);
g1.value
.then(res1 => {
console.log('res1:', res1);
const g2 = g.next(res1);
console.log('g2:', g2);
g2.value
.then(res2 => {
console.log('res2:', res2);
g.next(res2);
})
.catch(err2 => {
console.log(err2);
});
})
.catch(err1 => {
console.log(err1);
});
// g1: { value: Promise { <pending> }, done: false }
// res1: { "a": 1 }
// g2: { value: Promise { <pending> }, done: false }
// res2: { "b": 2 }
Приведенный выше кодGeneratorа такжеcallbackВ сочетании с асинхронной реализацией видно, что ручное выполнение по-прежнему требуется..thenДобавляйте обратные вызовы слой за слоем, но из-заnext()метод возвращает объект{value: xxx,done: true/false}Поэтому мы можем упростить его и написать автоматический исполнитель:
function run(gen) {
const g = gen();
function next(data) {
const res = g.next(data);
// 深度递归,只要 `Generator` 函数还没执行到最后一步,`next` 函数就调用自身
if (res.done) return res.value;
res.value.then(function(data) {
next(data);
});
}
next();
}
run(function*() {
const res1 = yield Promise.resolve({a: 1});
console.log(res1);
// { "a": 1 }
const res2 = yield Promise.resolve({b: 2});
console.log(res2);
// { "b": 2 }
});
Сказав так много, почему оно еще не пришло?async/await, не волнуйся, гость, оно грядет (на самом деле я пропустил какой-то контент и не сказал: взаимосвязь Promise и callback, функция thunk, библиотека co, если интересно, можешь зайти в google, учитель Руаньифэн сказалНачало работы с es6Классно, буду пересматривать время от времени)
💡 Что выводит журнал ниже?
function* gen() {
const ask1 = yield "2 + 2 = ?";
console.log(ask1);
const ask2 = yield "3 * 3 = ?"
console.log(ask2);
}
const generator = gen();
console.log( generator.next().value );
console.log( generator.next(4).value );
console.log( generator.next(9).done );
справочный ответ
// 2 + 2 = ?
// 4
// 3 + 3 = ?
// 6
// true
Async/Await
первый,async/awaitдаGeneratorсинтаксический сахар для вышеперечисленногоЯ разделительная линияПервое предложение ниже уже было сказано, давайте посмотрим на сравнение двух:
// Generator
run(function*() {
const res1 = yield Promise.resolve({a: 1});
console.log(res1);
const res2 = yield Promise.resolve({b: 2});
console.log(res2);
});
// async/await
const aa = async ()=>{
const res1 = await Promise.resolve({a: 1});
console.log(res1);
const res2 = await Promise.resolve({b: 2});
console.log(res2);
return 'done';
}
const res = aa();
можно увидеть,async functionвместоfunction*,awaitвместоyield, а вам не нужно писать автоматический исполнителя себяrunохватывать
посмотри еще раз сейчасasync/awaitспециальность:
- когда
awaitКогда за ним следует объект Promise, он будет выполняться асинхронно, а другие типы данных будут выполняться синхронно. - воплощать в жизнь
const res = aa();Возвращаемый объект по-прежнему является объектом Promise в приведенном выше коде.return 'done';будет прямо подthenфункция получает
res.then(data => {
console.log(data); // done
});
Наконец, подведем итоги:
преимущество:
- Встроенный привод: Автономный привод
- лучшая семантика: чем звездочки и
yield, семантика понятнее - Более широкая применимость:
awaitПосле команды вы можете следоватьPromiseОбъекты и значения примитивных типов (это эквивалентно синхронной операции)
будь осторожен:
-
awaitпосле командыPromiseобъект, результат операции может бытьrejected, так что лучше всегоawaitкоманда наtry...catchв кодовом блоке -
awaitкоманду можно использовать только вasyncСреди функций, если они используются в обычных функциях, будет сообщено об ошибке - несколько
awaitАсинхронные операции после команды, если нет вторичной связи, лучше позволить им срабатывать одновременно (Promise.all) - Обратите внимание на его использование в переработке, попробуйте
for/for..of(итератор) используется в, никогда вforEach/filterиспользовать его и постараться не использовать его вmapиспользуется в - совместимость(caniuse,node.green) не очень, конечно, в целом можно использовать инструмент компиляции для выполнения polyfill (babel) или es6-shim (после конвертации сопрограмма, реализованная синтаксическим сахаром, неэффективна,
co + generatorСравниватьcbспособ плохой работы) - Может использоваться в функциях жизненного цикла, онлайн-пример:React,Vue
- Захват ошибок: если вам нужно зафиксировать несколько ошибок и обрабатывать их по-разному, вы можете рассмотреть возможность предоставления
awaitПослеpromiseобъект добавленcatchфункцию, для которой нужно написатьhelper:
// to.js
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
/***使用***/
import to from './to';
async function asyncTask() {
const [err1, res1] = await to(fn1);
if(!res1) throw new CustomerError('No res1 found');
const [err2, res2] = await to(fn2);
if(err) throw new CustomError('Error occurred while task2');
}
💡 Учитывая массив URL-адресов, как реализовать последующие и параллельные интерфейсы?
справочный ответ
// 继发一
async function loadData() {
var res1 = await fetch(url1);
var res2 = await fetch(url2);
var res3 = await fetch(url3);
return "when all done";
}
// 继发二
async function loadData(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
/********/
// 并发一
async function loadData() {
var res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)]);
return "when all done";
}
// 并发二
async function loadData(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);
}
}
Ах, наконец-то все конченоasync-awaitС таким количеством очков знаний я надеюсь помочь вам при использовании его в будущем.
【Ссылаться на】:
- developer.Mozilla.org/this-cn/docs/…
- Голод 6. Ruan Yifeng.com/#docs/ITER A…
- es6.ruanyifeng.com/#docs/async
===🧐🧐Недостаточно в тексте, прошу исправить🤪🤪===