предисловие
За последние два года новые технологии во внешнем интерфейсе были ошеломляющими, и один за другим появлялись различные фреймворки и инструменты. Недавно я планирую сделать обзор основ js, прочная основа — краеугольный камень изучения новых технологий. Эта статья кратко суммирует базовые знания об асинхронности js в качестве заметки для чтения.
На данный момент в этой серии четыре статьи:
Эта статья была впервые опубликована в моем личном блоге :(Woohoo.Fe record.com/Professor Lu-A sync.…). Он будет обновляться и пересматриваться в будущем.При перепечатке, пожалуйста, прикрепите исходный адрес для отслеживания.
содержание
легенда
Перезвоните
Обратные вызовы — наиболее распространенный способ написания и обработки асинхронной логики в программах на JavaScript.Будь то setTimeout или ajax, он использует обратные вызовы для выполнения того, что мы намереваемся сделать в определенный момент.
Общая форма использования обратных вызовов
// request(..) 是个支持回调的请求函数
request('http://my.data', function callback(res) {
console.log(res)
})
// 或者延时的回调
setTimeout(function callback() {
console.log('hi')
}, 1000)
Функция обратного вызова — это функция обратного вызова, которая передается в функцию запроса в качестве параметра и будет вызываться и выполняться в соответствующее время.
проблема с обратным вызовом
Обратный вызов в основном имеет следующие две проблемы.
1. Отсутствие линейного понимания способностей, обратное ад
Слишком глубокая вложенность приводит к аду обратных вызовов, и сложно отследить порядок выполнения обратных вызовов.
2. Инверсия доверия управления отсутствует, и обработка ошибок не может быть гарантирована.
Логика вызова функции обратного вызова находится внутри функции запроса, и мы не можем гарантировать, что функция обратного вызова будет вызвана правильно. Сам обратный вызов не имеет механизма обработки ошибок и требует дополнительного проектирования. Возможные ошибки включают в себя: обратные вызовы, возвращающие неправильные результаты, проглатывание возможных ошибок и исключений, обратные вызовы, которые не выполняются, обратные вызовы, выполняемые несколько раз, обратные вызовы, выполняемые синхронно и т. д.
Promise
Распространенная форма использования промисов
Экземпляры Promise могут быть созданы с помощью конструктора Promise(..):
var p = new Promise(function(resovle, reject) {
if(1 > 0){
resolve() // 通常用于完成
} esle {
reject() // 用于拒绝
}
})
var onFulfilled = function() {} // 用于处理完成
var onRjected = function() {} // 用于处理拒绝
p.then(onFulfilled, onRjected)
Сначала поймите несколько терминов: разрешить, выполнить и отклонить.
И выполнение, и отклонение легко понять, одно выполняется, а другое отвергается. Комментарий resolve() в моем коде выше "обычно используется для завершения", потому что разрешение означает разрешение. Если вы передадите значение отклонения для разрешения, оно вернет отклонение, например, разрешить (Promise.reject()).
Разница между обещанием и обратным вызовом
Предполагая, что request(..) является функцией запроса:
// 回调的写法
request('http://my.data', function onResult(res) {
if(res.error) {
// 处理错误
}
// 处理返回数据
})
// Promise 的写法
var p = request('http://my.data');
p.then(function onFullfill(res) {
// 处理返回数据
})
.catch(function onRjected() {
// 处理错误
})
Обещания не заменяют обратные вызовы. Обещания обеспечивают надежный промежуточный механизм для управления обратными вызовами между кодом обратного вызова и асинхронным кодом, который будет выполнять задачу.
При использовании обратных вызовов уведомления — это обратные вызовы, вызываемые задачей request(..) . С промисами мы меняем это отношение на противоположное, прослушиваем события от request(..) , а затем, получив уведомление, продолжаем по мере необходимости.
Вы, должно быть, заметили, что промисы не избавляют полностью от обратных вызовов. Они просто меняют место, где передается обратный вызов. Вместо того, чтобы передавать обратный вызов в request(..), мы получаем что-то от request(..) (что выглядит как настоящий промис) и передаем этому обратному вызову.
Нормализация промиса обеспечивает согласованность поведения, а промис дает определенные значения, разрешение, отклонение и ожидание. Как только промис разрешается, он остается в этом состоянии навсегда. В этот момент он становится неизменяемым значением значение), которое можно просматривать столько раз, сколько необходимо.
статический метод
Promise.all()
Метод ProMise.all (iTerable) Возвращает обещание. Параметр iTerable представляет собой массив. Когда все PROMISE в параметре iTerable завершены (разрешить) или когда параметр не содержит обещания, метод возвращает завершение (разрешение). Этот метод возвращает отказ, когда есть промис, возвращающий отказ.
Для Promise.all([ .. ]) возвращенное обещание может быть выполнено только в том случае, если все переданные обещания выполнены. Если какие-либо обещания отклоняются, возвращаемое основное обещание немедленно отклоняется (отбрасывает результаты любых других обещаний). Если он завершится, вы получите массив со значениями выполнения всех переданных промисов. В случае отклонения вы получаете значение причины отклонения только для первого отклоненного обещания. Этот узор традиционно называют дверью: дверь открывается, когда все там.
Строго говоря, значения в массиве, передаваемом Promise.all([..]), могут быть Promises, thenables или даже непосредственными значениями. По сути, каждое значение в списке фильтруется с помощью Promise.resolve(..), чтобы убедиться, что это настоящий промис, которого нужно ждать, поэтому непосредственное значение нормализуется до промиса, созданного для этого значения. Если массив пуст, основное обещание выполняется немедленно.
Уведомление:
Если Promise.all([ .. ]) передать пустой массив, он завершится немедленно, но Promise.race([ .. ]) зависнет и никогда не разрешится.
Promise.race()
Метод Promise.race(iterable) возвращает обещание вместе с возвращаемым значением, которое разрешается в объект обещания или причину ошибки для отклонения. Параметр iterable представляет собой массив, если в iterable есть объект обещания для «разрешения» или «отклонения».
Для Promise.race([ .. ]) выигрывает только первое разрешенное обещание (либо выполненное, либо отклоненное), и результатом его разрешения становится разрешение, возвращающее обещание. Этот шаблон традиционно называют дверной защелкой: первый пришедший открывает дверную защелку, чтобы пройти.
Уведомление:
Для конкурса требуется как минимум один «участник». Таким образом, если вы передаете пустой массив, основное обещание расы ([..]) никогда не будет разрешено, не сразу. Легко выстрелить себе в ногу! ES6 должен указать, что он завершает или отклоняет, или просто выдает какую-то ошибку синхронизации. К сожалению, поскольку библиотека Promise старше ES6 Promises, им пришлось перенести эту проблему, поэтому будьте осторожны и никогда не передавайте пустой массив.
var p1 = Promise.resolve( 42 );
var p2 = Promise.resolve( "Hello World" );
var p3 = Promise.reject( "Oops" );
Promise.race( [p1,p2,p3] )
.then( function(msg){
console.log( msg ); // 42
} );
Promise.all( [p1,p2,p3] )
.catch( function(err){
console.error( err ); // "Oops"
} );
Promise.all( [p1,p2] )
.then( function(msgs){
console.log( msgs ); // [42,"Hello World"]
} );
Пример использования all и race
Promise.reject()
Метод Promise.reject(reason) возвращает обещание, отклоненное по причине.
Следующие два обещания эквивалентны:
var p1 = new Promise( function(resolve,reject){
reject( "Oops" );
});
var p2 = Promise.reject( "Oops" );
Promise.resolve()
Метод Promise.resolve(value) возвращает объект Promise, разрешенный с заданным значением. Но если это значение является затемным (то есть с методом then), возвращенное обещание будет «следовать» за объектом, допускающим выполнение, принимая его конечное состояние (разрешено/отклонено/ожидание/установлено); в противном случае оно вернется с этим значением. как объект обещания состояния успеха.
Метод прототипа
Promise.prototype.then()
then(..) принимает один или два аргумента: первый для завершения обратного вызова и второй для отклонения обратного вызова. Если какой-либо из них опущен или передан как нефункциональное значение, он будет заменен соответствующим обратным вызовом по умолчанию. Обратный вызов завершения по умолчанию просто передает сообщение, в то время как обратный вызов отклонения по умолчанию просто повторно выдает (распространяет) причину ошибки, которую он получил.
p.then( fulfilled );
p.then( fulfilled, rejected );
Promise.prototype.catch()
catch(..) принимает в качестве аргумента только обратный вызов отклонения и автоматически заменяет обратный вызов завершения по умолчанию. Другими словами, это эквивалентно then(null,..):
p.catch( rejected ); // 或者p.then( null, rejected )
then(..) и catch(..) также создают и возвращают новое обещание, которое можно использовать для реализации цепочки промисов. Если в обратном вызове завершения или отклонения возникает исключение, возвращенное обещание отклоняется. Если какой-либо из обратных вызовов возвращает не-Promise, не подлежащее последующему немедленному значению, это значение будет использоваться в качестве значения выполнения возвращенного обещания. Если обработчик завершения возвращает обещание или то, что можно использовать, это значение будет расширено и использовано в качестве значения разрешения возвращенного обещания.
Ограничения обещаний
1. Обработка ошибок последовательности
Ограничения дизайна промисов (в частности, то, как они объединены в цепочку) создают легкую ловушку, в которую легко попасть, где ошибки в цепочках промисов можно легко непреднамеренно проигнорировать.
Например:
// foo(..), STEP2(..)以及STEP3(..)都是支持promise的工具
var p = foo( 42 )
.then( STEP2 )
.then( STEP3 );
p.catch( handleErrors );
Если какой-либо шаг в цепочке фактически делает свое собственное обращение с ошибками (возможно, скрытым или абстрактным невидимым способом), то ваш окончательный улов не будет уведомлен. Это может быть то, что вы хотите - это «обрабатываемое отклонение» ведь - но это не может быть. Полная неспособность получить уведомление об ошибках (для любых «обработанных ошибок отторжения) также является недостатком, который ограничивает функциональность для некоторых случаев использования.
2. Одно значение
По определению обещание может иметь только одно значение выполнения или одну причину отклонения. Если вы хотите, чтобы обработчик получил несколько результатов, вы можете использовать только массив или объект для инкапсуляции результатов, которые должны быть переданы. так:
function foo (a) {
var b= a + 1;
return new Promise(resolve => {
resolve([a, b])
})
}
foo(1).then(function(msg) {
console.log(msg[0], msg[1]) // 1, 2
})
Это решение работает, но уродливо и громоздко выполнять перенос и развертывание на каждом этапе цепочки Promise.
Есть два способа инкапсулировать и распаковать одно значение:
- [1] Разделить значение, то есть разложить проблему на сигнал двух или более промисов.
function getB(a) {
return new Promise(resolve => {
return resolve(a + 1)
});
}
function foo(a) {
return [
Promise.resolve(a),
getB(a)
];
}
Promise.all(foo(1))
.then(function (msg){
console.log(msg[0], msg[1]) // 1, 2
});
Ну, этот не кажется лучше первого, но выглядит более хлопотно. Но такой подход больше соответствует философии дизайна Promises. Этот метод намного проще, если вам позже потребуется реорганизовать код, чтобы разделить вычисление a и b. Гораздо чище и гибче позволить вызывающему коду решить, как упорядочить два промиса, а не абстрагировать эту деталь внутри foo(..) .
- [2] Разверните/передайте параметры, используйте команду «apply» или деструктурирование es6 для удаления одного значения.
var p = new Promise (resolve => {
return resolve([1,2])
})
// 使用 apply
p.then(Function.prototype.apply(function(a, b){
console.log(a, b) // 1, 2
})
// 使用解构
p.then(function([a, b]) {
console.log(a, b) // 1, 2
})
Подводя итог, одно значение является одним из ограничений Promise, поэтому, если нам нужно обработать результат с несколькими параметрами, мы можем только инкапсулировать результат в коллекцию, такую как объект или массив, а затем использовать различные методы для обработки. уничтожить его в функции-обработчике.
3. Одно разрешение
Одной из наиболее важных особенностей обещаний является то, что обещания могут быть разрешены только один раз (завершены или отклонены).
Таким образом, следующий код является проблематичным:
var p = new Promise(function(resolve) {
$('.mybtn').click(resolve)
})
p.then(function(e) {
var btnId = evt.currentTarget.id;
return fetch('http://myurl.url/?id=' + btnId)
})
.then(function(res) {
console.log(res)
})
Этот подход будет работать только в том случае, если вашему приложению нужно только один раз отреагировать на нажатие кнопки. Если кнопка нажата во второй раз, обещание p разрешено, поэтому второй вызов resolve(..) будет проигнорирован.
Поэтому вам может понадобиться изменить эту парадигму и создать совершенно новую цепочку промисов для каждого события:
$('#mybtn').click(function(e) {
var btnId = evt.currentTarget.id;
fetch('http://myurl.url/?id=' + btnId)
.then(function(res) {
console.log(res)
})
});
Этот подход работает, потому что для каждого события «щелчка» на кнопке запускается совершенно новая последовательность промисов. Это некрасиво из-за необходимости определять всю цепочку промисов в обработчике событий. Кроме того, такой дизайн в некоторой степени подрывает разделение задач и функций (Separation of concept, SoC или разделение задач). Вы, вероятно, захотите поместить определение обработчика события и определение ответа на событие (эту цепочку промисов) в разные места вашего кода. Этого трудно добиться в этом режиме без вспомогательного механизма.
4. Инерция
Весь существующий код еще не понимает промисы, вам нужно инкапсулировать функцию, которую нужно вызвать обратно, в функцию, поддерживающую промисы.
5. Обещания, которые нельзя отменить
После того, как промис создан и для него зарегистрированы обработчики завершения и/или отклонения, у вас нет возможности извне остановить его процесс, если произойдет что-то, что оставит задачу незавершенной.
6. Выполнение обещаний
Сравните базовую цепочку асинхронных задач на основе обратного вызова с количеством частей, которые необходимо переместить в цепочке обещаний. Очевидно, что обещания требуют больше действий, что, естественно, означает, что они также немного медленнее.
Строитель
Глоссарий
Строитель(Generator)
генератор - этофункция, которая возвращает итератор, обозначенный знаком * после ключевого слова function.
итератор(Iterable)
итераторобъект, который имеет некоторые проприетарные интерфейсы, разработанные специально для итеративного процесса, все объекты итераторов имеют метод next, который возвращает объект результата для каждого вызова. Объект результата имеет два свойства: одно значение, представляющее следующее возвращаемое значение, другое свойство done, логическое значение, которое возвращает значение true, когда больше нет данных для возврата. Итератор также содержит внутренний указатель на положение значения в текущей коллекции, и каждый вызов метода next() возвращает следующее доступное значение.
повторяемый объект(Iterator)
повторяемый объектИмеет свойство Symbol.iterator, объект, тесно связанный с итераторами. Symbol.iterator возвращает итератор, который воздействует на прикрепленный объект через указанную функцию. В ECMCScript 6 все объекты коллекций (коллекции массивов, наборов и карт) и строки являются повторяемыми объектами, и эти объекты имеют итераторы по умолчанию.
Кроме того, поскольку генераторы по умолчанию используют свойство Symbol.iterator, все итераторы, созданные с помощью генераторов, являются итерируемыми.
цикл for-of
Каждый раз, когда выполняется цикл for-of, вызывается метод next() интерфейса итератора итерируемого объекта, а свойство value объекта результата, возвращаемого итератором, сохраняется в переменной. Значение свойства равно true.
Общая форма использования генераторов
function *foo() {
var x = yield 2;
var y = x * (yield x + 1)
console.log( x, y );
return x + y
}
var it = foo();
it.next() // {value: 2, done: false}
it.next(3) // {value: 4, done: false}
it.next(3) // 3 9, {value: 12, done: true}
Пара yield.. и next(..) объединяются, чтобы сформировать двустороннюю систему передачи сообщений во время выполнения генератора.
Несколько замечаний:
- Как правило, требуется на один вызов next(..) больше, чем оператор yield, в предыдущем фрагменте кода есть два вызова yield и три вызова next(..);
- Первый next(..) всегда запускает генератор и работает до первого yield ;
- Каждый yield.. в основном задает вопрос: «Какое значение я должен здесь вставить?», на который отвечает next next(..) . Второй next(..) отвечает на вопрос для первого yield.. , третий next(..) отвечает на вопрос для второго yield и так далее;
- yield.. поскольку выражение может генерировать сообщение в ответ на вызов next(..), а next(..) также может отправлять значение приостановленному выражению yield.
Используйте генераторы для создания итерируемых объектов
var obj = {
[Symbol.iterator]: function *() {
var result = 1
while(result < 500) {
result = result * 2
yield result
}
}
}
for(let value of obj) {
console.log(value)
}
// 2 4 8 16 32 64 128 256 512
Асинхронный итерационный генератор
Взгляните на следующий код, мы получаем функцию запроса в генераторе (приостанавливаем генератор, чтобы продолжить выполнение, и одновременно выполняем функцию запроса), после того, как генератор выполнения создает итерируемый объект, мы передаем next() метод в функции запроса Получите результат запроса, передайте результат генератору и возобновите выполнение генератора.
function foo() {
ajax('http://my.data', function(res) {
if(res.error) {
// 向*main()抛出一个错误
it.throw(res.error)
}
// 用收到的data恢复*main()
it.next(res.data)
})
}
function *main() {
try {
var data = yeild foo();
console.log(data)
} catch(e) {
console.error(e)
}
}
var it = main();
// 这里启动!
it.next();
В этом примере мы инициируем запрос foo() в *main(), а затем делаем паузу; затем восстанавливаем соответствующие данные в foo() и продолжаем выполнение *mian() и передаем результат foo() через next( ).
По сути, мы абстрагируемся от асинхронности как детали реализации, позволяя нам отслеживать управление потоком в синхронном порядке: «Сделайте запрос Ajax, распечатайте ответ, когда он будет выполнен». контроль, и эта выразительная сила бесконечно масштабируема, так что мы можем выразить столько шагов, сколько нам нужно.
У нас, казалось бы, идеально синхронный код внутри генератора (за исключением самого ключевого слова yield), но за кулисами скрыто то, что выполнение внутри foo(..) может быть полностью асинхронным. И реализация, казалось бы, синхронной обработки ошибок (через try..catch) в асинхронном коде также является огромным улучшением читабельности и здравомыслия.
Генератор + Обещание
Самый естественный способ максимизировать полезность промисов и генераторов — создать промис, а затем использовать этот промис для управления итератором генератора.
Рекомендуется посмотреть на следующий код, а затем обдумать вышеприведенный абзац в уме.
function foo() {
return fetch('http://my.data')
}
function *main() {
try {
var data = yeild foo();
console.log(data)
} catch(e) {
console.error(e)
}
}
var it = main();
var p = it.next().value; // p 的值是 foo()
// 等待 promise p 决议
p.then(
function(data) {
it.next(data); // 将 data 赋值给 yield
},
function(err) {
it.throw(err);
}
)
Таким образом, обещание + генератор реализовано для управления асинхронным процессом: foo() выполняется в *mian() для инициации запроса, итератор, сгенерированный *mian(), используется для получения результата разрешения обещания foo. (), а затем итерация выбирается для продолжения в соответствии с результатом или выдает ошибку.
Мы можем дождаться разрешения, реализацию следующего () процесса абстрагировано, автоматическое ожидание разрешения и продолжения до конца:
// 定义 run 函数
functiton run(gen) {
var args = [].slice.call(arguments, 1), it;
// 在当前上下文中初始化生成器
it = gen.apply(this, args);
// 返回一个 promise 用于生成器完成
return Promise.resolve()
.then(function handleNext(value) {
// 对下一个 yield 值出的值运行
var next = it.next(value);
return (function handleValue(next){
// 判断生成器是否运行完毕
if(next.done) {
return next.value;
}
// 否则继续运行
else {
return Promise.resolve(next.value)
.then(
// 成功就恢复异步循环,把决议的值发回生成器
handleNext,
// 如果 value 是被拒绝的 promise
// 就把错误传回生成器进行出错处理
function handleErr(err) {
return Promise.resolve(
it.throw(err)
)
}
)
}
})(next)
})
}
function foo(p) {
return fetch('http://my.data?p=' + p)
}
function *main(p) {
try {
var data = yeild foo(p);
console.log(data)
} catch(e) {
console.error(e)
}
}
// 运行!
run(main, '1')
Run () Функция роли, которую мы собираемся поговорить с функцией async / aNaiq, одинаково.
Async/Await
Что такое асинхронная функция? Одним словом, это синтаксический сахар для Генераторных функций. По форме она похожа на функцию run(..), которую мы только что написали.
Общее использование асинхронных функций
Основная форма использования асинхронной функции выглядит следующим образом:
function foo(p) {
return fetch('http://my.data?p=' + p)
}
async function main(p) {
try {
var data = await foo(p);
return data
} catch(e) {
console.error(e)
}
}
main(1)
.then(data => console.log(data))
Заметное отличие от функции генератора заключается в том, что*
сталasync
,yeild
сталawait
, и нам больше не нужно определять функцию run(..) для реализации комбинации Promise и Generator. Когда асинхронная функция выполняется, как только она встречает await, она сначала возвращается, ждет завершения асинхронной операции, затем выполняет оператор за телом функции и, наконец, возвращает объект Promise.
Обычно за командой await следует объект Promise. Если нет, он будет преобразован в объект Promise, который разрешается немедленно. Если объект Promise после команды await переходит в состояние reject, параметр reject будет получен функцией обратного вызова метода catch.
Преимущества асинхронных функций
Улучшение асинхронной функции по сравнению с функцией генератора отражено в следующих четырех пунктах.
1. Встроенный актуатор.
У асинхронной функции есть встроенный исполнитель (похожий на функцию run(..), которую мы только что реализовали внутри), что избавляет нас от необходимости вручную перебирать генератор;
2. Лучшая семантика.
async и await имеют более четкую семантику, чем звездочки и yield. async означает, что в функции есть асинхронная операция, а await означает, что следующему выражению необходимо дождаться результата.
3. Более широкая применимость.
Согласно Конвенции модульной CO, команда выходов может сопровождаться функцией Thunk или объектом обещания, в то время как команда avait a async-функции может сопровождаться объектом для обещания и примитивными значениями типа (числовая, строка и логическое значение. значения, но это эквивалентно синхронному операции).
4. Возвращаемое значение - Promis **
Возвращаемое значение асинхронной функции — это объект Promise, что намного удобнее, чем возвращаемое значение функции Generator, являющейся объектом Iterator. Вы можете указать следующее действие с помощью метода then.
Примечания по использованию асинхронных функций
При использовании асинхронных функций необходимо отметить три момента:
1. Как упоминалось ранее, объект Promise, стоящий за командой await, может быть отклонен, поэтому лучше всего поместить команду await в блок кода try...catch.
2. Для асинхронных операций, следующих за несколькими командами await, если нет вторичной связи, лучше всего позволить им запускаться одновременно.
3. Команду await можно использовать только в асинхронных функциях, при использовании в обычных функциях будет выдано сообщение об ошибке.
//getFoo 与 getBar 是两个互相独立、互不依赖的异步操作
// 错误写法,会导致 getBar 在 getFoo 完成后才执行
let foo = await getFoo();
let bar = await getBar();
// 正确写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 正确写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
Асинхронные операции без вторичной связи должны запускаться синхронно.
асинхронный генератор
объяснение имени
асинхронный генератор(Async Generator)
Точно так же, как функция генератора возвращает объект синхронного итератора, роль функции асинхронного генераторавозвращает асинхронный итераторобъект.
Синтаксически асинхронная функция-генератор представляет собой комбинацию асинхронной функции и функции-генератора.
асинхронный итератор(Async Iterator)
Подобно итератору, асинхронный итератор также является объектом и имеет метод next.В отличие от итератора, метод next итератора возвращает структуру возвращаемого объекта при каждом вызове.{value, done}
,вvalue
представляет значение текущих данных,done
— логическое значение, указывающее, завершена ли итерация. . И следующий метод асинхронного итератораКаждый раз, когда он возвращает объект Promise, подождите, пока объект Promise разрешится, а затем верните{value, done}
структурный объект. Другими словами, окончательное поведение асинхронных итераторов такое же, как и у синхронных итераторов, за исключением того, что объект Promise будет возвращен первым в качестве посредника.
Для обычных итераторовnext
Метод должен быть синхронным и возвращать значение сразу после вызова. То есть после выполненияnext
метод, он должен быть получен синхронноvalue
а такжеdone
эти два свойства. Синхронные итераторы не работают, если нам нужно перебирать асинхронные данные. Например, в следующем коде функция readLinesFromFile() не может отображать свои асинхронные данные через синхронный итератор:
// readLinesFromFile 是一个异步返回数据的函数
for (const line of readLinesFromFile(fileName)) {
console.log(line);
}
ES2018 представил «Асинхронный итератор», который предоставляет собственный интерфейс итератора для асинхронных операций, а именноvalue
а такжеdone
Оба свойства генерируются асинхронно.
asyncIterator
.next()
.then(
({ value, done }) => /* ... */
);
Самой большой грамматической особенностью асинхронных итераторов является вызов итератора.next
метод, который возвращает объект Promise.
Ниже приведен более конкретный пример асинхронного итератора.
// createAsyncIterable(..) 是一个创建可异步迭代对象的函数,我们稍后解释它
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next()
.then(iterResult1 => {
console.log(iterResult1); // { value: 'a', done: false }
return asyncIterator.next();
})
.then(iterResult2 => {
console.log(iterResult2); // { value: 'b', done: false }
return asyncIterator.next();
})
.then(iterResult3 => {
console.log(iterResult3); // { value: undefined, done: true }
});
Из-за асинхронного итератораnext
метод, который возвращает объект Promise. Следовательно, он может быть помещен вawait
Команда позади.
async function foo() {
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
console.log(await asyncIterator.next());
// { value: 'a', done: false }
console.log(await asyncIterator.next());
// { value: 'b', done: false }
console.log(await asyncIterator.next());
// { value: undefined, done: true }
}
Асинхронно повторяемый объект(Async Iterable)
повторяемый объектИмеет свойство Symbol.asyncIterator., мы знаем, что интерфейс синхронного итератора объекта, развернутый вSymbol.iterator
свойства выше. Аналогично, интерфейс асинхронного итератора объекта, развернутый вSymbol.asyncIterator
свойства выше. Неважно, какой объект, пока егоSymbol.asyncIterator
Если свойство имеет значение, это означает, что оно должно проходиться асинхронно.
цикл for-wait-of
Цикл for-of используется для обхода синхронизированного интерфейса Iterator. Недавно представленный цикл for-await-of представляет собой интерфейс asyncIterator для обхода асинхронных операций.
// createAsyncIterable 是一个创建可异步迭代对象的函数
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// Output:
// a
// b
Если объект Promise, возвращенный следующим методом, отклонен, for-await-of сообщит об ошибке и использует try...catch, чтобы поймать ее.
function createRejectingIterable() {
return {
[Symbol.asyncIterator]() {
return this;
},
next() {
return Promise.reject(new Error('Problem!'));
},
};
}
(async function () { // (A)
try {
for await (const x of createRejectingIterable()) {
console.log(x);
}
} catch (e) {
console.error(e);
// Error: Problem!
}
})(); // (B)
Также for-await-of можно использовать для перебора синхронизированных итерируемых объектов.
(async function () {
for await (const x of ['a', 'b']) {
console.log(x);
}
})();
// Output:
// a
// b
for-await-of преобразует значение каждой итерации в Promise через Promise.resolve().
Общая форма использования асинхронных генераторов
Синтаксически асинхронная функция-генератор представляет собой комбинацию асинхронной функции и функции-генератора.
async function *createAsyncIterable() {
var x = yield 2;
var y = x * (yield x + 1)
return x + y
}
var it = createAsyncIterable()
function onFulfilled(obj){
console.log(obj)
}
it.next().then(onFulfilled) // {value: 2, done: false}
it.next(3).then(onFulfilled) // {value: 4, done: false}
it.next(3).then(onFulfilled) // 3 9, {value: 12, done: true}
Создавайте асинхронно итерируемые объекты с асинхронными генераторами
var obj = {
[Symbol.asyncIterator]: async function *gen() {
var result = 1
while(result < 500) {
result = result * 2
yield result
}
}
};
(async function foo () {
for await (const x of obj) {
console.log(x);
}
})();
// 2 4 8 16 32 64 128 256 512
После появления асинхронных функций-генераторов JavaScript имеет четыре функциональные формы: обычные функции, асинхронные функции, функции-генераторы и асинхронные функции-генераторы. Обратите внимание на различия между каждой функцией. По сути, если это серия асинхронных операций, которые выполняются последовательно (например, чтение файла, затем запись нового содержимого и сохранение на диск), вы можете использовать асинхронную функцию; если это серия асинхронных операций, которые производят одни и те же данные структуру (например, одну строку для чтения файла), вы можете использовать функцию асинхронного генератора.
Основы асинхронности
Асинхронность в JS
Каждый раз, когда вы заключаете фрагмент кода в функцию и указываете, что он будет выполняться в ответ на событие (таймер, щелчок мыши, ответ Ajax и т. д.), вы создаете в своем коде блок, который будет выполняться в будущем, и, таким образом, В этой программе реализован асинхронный механизм.
Между несколькими асинхронными объектами могут существовать следующие три отношения:
- неинтерактивный
- взаимодействовать
- сотрудничество
цикл событий(event loop)
Среда выполнения js предоставляет механизм для обработки выполнения нескольких блоков в программе, и механизм JavaScript вызывается при выполнении каждого блока.Этот механизм называется циклом обработки событий.
Основной поток считывает события из «очереди задач», и этот процесс цикличен, поэтому весь механизм работы также называется Event Loop.
Концептуальная модель среды выполнения JavaScript- Стек: вызовы функций образуют кадр стека.
- Куча: объекты размещаются в куче, для их представления используется большая неорганизованная область памяти.
- Очередь. Среда выполнения JavaScript содержит очередь ожидающих сообщений (также известную как «очередь событий»). Каждое сообщение связано с функцией (называемой «функцией обратного вызова»). Когда стек пуст, сообщение удаляется из очереди для обработки. Эта обработка состоит из вызова функции, связанной с сообщением (и, таким образом, создания начального кадра стека). Когда стек снова пуст, это означает, что обработка сообщения завершена, и тогда можно обрабатывать следующее сообщение. Это процесс «цикла событий».
очередь задач(очередь заданий)
В ES6 была введена новая концепция под названием «Очередь заданий». Это слой поверх очереди цикла событий. К сожалению, на данный момент это механизм без открытого API, поэтому его немного сложно показать. Поэтому мы пока опишем это только концептуально.
Я думаю, что лучший способ понять очередь задач состоит в том, что это очередь, которая зависает после каждого тика очереди цикла событий. В каждом такте цикла событий возможное асинхронное действие не приведет к добавлению полного нового события в очередь цикла событий, но добавит элемент (задачу) в конец очереди задач для текущего такта.
Это все равно, что сказать: «О, есть еще одно дело, которое нужно сделать в будущем, но убедитесь, что вы сделаете это, прежде чем произойдет что-то еще».
Очередь цикла событий похожа на игру в парке развлечений: после игры вам нужно встать в очередь в конце очереди, чтобы снова сыграть в нее. Очередь задач аналогична игре в игру, прерывание очереди и продолжение игры.
Задача может вызвать больше задач, добавленных к концу очередью. Таким образом, теоретически, цикл задач (цикл задания) может быть бесконечным циклом (задача всегда добавляет другая задача, и так далее), которая привела к голодаю программу, не может быть передана на следующий цикл событий.
Задачи похожи по идее на хак setTimeout(..0), но их реализация более четко определена, а порядок более гарантирован.
Асинхронный характер промисов основан на задачах.
Условия гонки, ворота, защелки(состояние гонки, ворота и защелка)
Взгляните на кусок кода:
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3;
}
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
Мы не можем определить последние значения a и b перед выполнением программы, потому что их значения зависят от того, какие из foo и bar выполняются первыми, а в этом коде мы не можем определить, кто выполнится первым.
состояние гонки: В функциях JavaScript эта неопределенность в отношении порядка функций обычно называется состоянием гонки, когда foo() и bar() соревнуются друг с другом, чтобы определить, кто бежит первым. В частности, это состояние гонки, потому что окончательный результат a и b нельзя надежно предсказать.
дверь: Его характеристики можно описать как «все проходы перед проходами». Форма if (a && b) традиционно называется дверью, и мы не можем сказать, в каком порядке прибывают a и b, но ждем, пока они оба будут готовы открыть дверь дальше. В терминах классического программирования шлюз — это механизм, который ожидает завершения двух или более параллельных/одновременных задач, прежде чем продолжить. Порядок, в котором они выполняются, не важен, но все они должны быть выполнены до того, как дверь можно будет открыть и продолжить управление потоком.
защелка: Его характеристики можно охарактеризовать как «выигрывает только первое место». До финиша должна быть «гонка», и есть только один победитель.
последовательный, одновременный(sequential & concurrency)
Последовательные и параллельные относятся к структуре проекта несвязанных задач.
заказОтносится к последовательному выполнению нескольких задач.
параллелизмПараллельная программа — это программа, которая может выполнять различные задачи, которые обычно не связаны между собой, в одно и то же время. Параллелизм — это обработка большого количества задач различными компонентами системы или модуля в течение определенного периода времени с упором на структуру и планирование.
Например, одновременный вызов во время еды является параллелизмом.
последовательный, параллельный(последовательный и параллелизм)
Последовательные и параллельные относятся к способу выполнения одной задачи.
сериалОтносится к последовательному выполнению нескольких шагов одной задачи.
параллельноПараллельность — это выполнение нескольких шагов несколькими способами, то есть несколько шагов одной задачи выполняются одновременно.
Ссылаться на:Например, едят рис и овощи съели чучел рот, который параллелен.
Concurrency is not parallelism
В чем разница между параллелизмом и параллелизмом? _жиху
Разница между Concurrency и Parallelism_vaikan
Все еще интересуетесь параллелизмом и параллелизмом? _laike9m
Также поговорим о параллелизме и parallelism_tonybai
процесс, поток(процесс и поток)
Наиболее распространенными инструментами для параллельных вычислений являютсяобработатьа такженить, процессы и потоки выполняются независимо и, возможно, одновременно, а несколько потоков могут совместно использовать память одного процесса.
Процесс - это программа с определенными независимыми функциями. Это независимая единица системы для распределения и планирования ресурсов. Основное внимание уделяется планированию системы и отдельным единицам. Другими словами, процесс - это программа, которая может работать независимо.
Поток — это сущность процесса и базовая единица планирования и назначения ЦП.Это базовая единица, меньшая, чем процесс, который может работать независимо, и сам поток в основном не владеет системными ресурсами. Во время выполнения временно используются только некоторые счетчики, регистры и стеки.
Отношения между ними:
- Поток может принадлежать только одному процессу, а процесс может иметь несколько потоков, но по крайней мере один поток (обычно основной поток).
- Ресурсы выделяются процессу, и все потоки одного и того же процесса совместно используют все ресурсы этого процесса.
- Потоки нуждаются в совместной синхронизации во время процесса выполнения. Потоки различных процессов должны быть синхронизированы посредством обмена сообщениями.
- Процессор назначается потоку, то есть поток фактически выполняется на процессоре.
- Поток — это исполнительная единица внутри процесса и планируемая сущность внутри процесса.
Разница между ними:
- Планирование: поток — это основная единица планирования и распределения, а процесс — основная единица владения ресурсами.
- Параллелизм: не только процессы могут выполняться одновременно, но и несколько потоков одного и того же процесса могут выполняться одновременно.
- Владение ресурсами: процесс является независимой единицей, которая владеет ресурсами.Потоки не владеют системными ресурсами, но могут получать доступ к ресурсам, принадлежащим процессу.
В чем разница между процессом и потоком? _жиху
Гранулярность поочередного выполнения параллельных потоков и поочередного планирования асинхронных событий совершенно различна. Цикл событий делит свою работу на задачи и выполняет их последовательно, не допуская параллельного доступа и модификации разделяемой памяти. Параллельное и последовательное выполнение может сосуществовать с циклами событий в отдельных потоках, которые взаимодействуют друг с другом.