Поток управления — это порядок, в котором интерпретатор JavaScript выполняет инструкции. Если сценарий не содержит операторов, изменяющих его ход, он выполняется от начала до конца, по одной строке за раз. Структуры управления используются для определения того, выполняется ли набор операторов на основе определенного набора критериев, повторно выполняется набор операторов или прерывается последовательность операторов.
Условные операторы
Условные операторы определяют, должен ли код выполняться на основе одного или нескольких условий. Условный оператор выполняет содержащийся в нем код, если связанное условие (или набор условий) имеет значение true
. В противном случае код пропускается.
if
… else
Оператор if
оценивает условие внутри следующих за ним совпадающих круглых скобок. Если условие внутри круглых скобок оценивается как true
, выполняется оператор или оператор блока , следующий за совпадающими круглыми скобками:
if ( true ) console.log( "True." );
> "True."
if ( true ) {
const myString = "True.";
console.log( myString );
}
> "True."
Если условие внутри круглых скобок имеет значение false
, следующий за ним оператор игнорируется:
if ( false ) console.log( "True." );
Ключевое слово else
, следующее сразу за оператором if
и его оператором условного выполнения, указывает оператор, который будет выполнен, если условие if
принимает значение false
:
if ( false ) console.log( "True." )''
else console.log( "False" );
> "False."
Чтобы объединить несколько операторов if
, вы можете сделать оператор условного выполнения следующим else
оператором if
:
const myCondition = 2;
if ( myCondition === 5 ) console.log( "Five." );
else if ( myCondition === 2 ) console.log( "Two." );
Мы настоятельно рекомендуем использовать синтаксис блочного оператора после условных операторов, чтобы улучшить читаемость, но else if
часто являются исключением:
if ( myCondition === 5 ) {
console.log( "Five." );
} else if ( myCondition === 3 ) {
console.log( "Three" );
} else {
console.log( "Neither five nor three." );
}
> "Neither five nor three."
Тернарный оператор
if
условно выполняет оператор. Тернарный оператор (точнее, но реже называемый тернарным условным оператором ) — это сокращение, используемое для условного выполнения выражения. Как следует из названия, тернарный оператор — единственный оператор JavaScript, который использует три операнда:
- Условие, которое необходимо оценить, за которым следует вопросительный знак (
?
). - Выражение, которое выполняется, если условие оценивается как
true
, за которым следует двоеточие (:
. - Выражение, которое будет выполнено, если условие окажется
false
.
Это часто используется для условной установки или передачи значения:
const myFirstResult = true ? "First value." : "Second value.";
const mySecondResult = false ? "First value." : "Second value.";
myFirstResult;
> "First value."
mySecondResult;
> "Second value."
switch
… case
Используйте оператор switch
, чтобы сравнить значение выражения со списком потенциальных значений, определенных с использованием одного или нескольких ключевых слов case
. Этот синтаксис необычен, поскольку он взят из самых ранних проектных решений JavaScript. Синтаксис switch
… case
использует ключевое слово switch
, за которым следует выражение, которое нужно вычислить, заключенное в круглые скобки, а затем соответствующая пара фигурных скобок. Тело switch
может содержать ключевые слова case
, обычно одно или несколько, за которыми следует выражение или значение, за которым следует двоеточие ( :
).
Когда интерпретатор достигает case
, значение которого соответствует выражению, оцениваемому в круглых скобках после ключевого слова switch
, он выполняет все инструкции, следующие за этим предложением case
:
switch ( 2 + 2 === 4 ) {
case false:
console.log( "False." );
case true:
console.log( "True." );
}
> "True."
Все операторы, следующие за соответствующим case
выполняются, даже если они заключены в оператор блока .
switch ( 2 + 2 === 4 ) {
case false:
console.log( "False." );
case true:
let myVariable = "True.";
console.log( myVariable );
}
> "True."
Одна из ошибок использования switch…case
заключается в том, что после обнаружения совпадения интерпретатор JavaScript выполняет любой оператор, следующий за совпавшим case
, даже те, которые находятся в других предложениях case
. Это называется «проваливанием» в следующий case
:
switch ( 2 + 2 === 7 ) {
case false:
console.log( "False." );
case true:
console.log( "True." );
}
> "False."
> "True."
Чтобы предотвратить провал, заканчивайте каждый случай ключевым словом break
, которое немедленно прекращает вычисление тела switch
:
switch ( 2 + 2 === 7 ) {
case false:
console.log( "False." );
break;
case true:
console.log( "True." );
break;
}
> "False."
Если ни один case
не соответствует условному значению, switch
выбирает предложение default
, если оно есть:
switch ( 20 ) {
case 5:
console.log( "The value was five." );
break;
case 10:
console.log( "The value was ten." );
break;
default:
console.log( "The value was something unexpected." );
}
> "The value was something unexpected."
Однако провал применим и к default
, что потенциально может привести к неожиданным результатам. Чтобы исправить это, завершите оператор default
командой break
или поместите его последним в списке случаев.
switch ( 20 ) {
default:
console.log( "The value was something unexpected." );
case 10:
console.log( "The value was ten." );
break;
case 5:
console.log( "The value was five." );
break;
}
> The value was something unexpected.
> The value was ten.
Поскольку предложения case
не требуют оператора блока для группировки нескольких операторов, предложения case
и default
сами по себе не создают лексическую область видимости :
let myVariable;
switch ( true ) {
case true:
let myVariable = "True.";
break;
default:
let myVariable = "False.";
break;
}
> Uncaught SyntaxError: redeclaration of let myVariable
Для управления областью действия используйте операторы блоков:
let myVariable;
switch ( true ) {
case true: {
let myVariable = "True.";
break;
}
default: {
let myVariable = "False.";
break;
}
}
Циклы и итерации
Циклы позволяют повторять набор операторов до тех пор, пока выполняется условие или пока не будет выполнено условие. Используйте циклы для выполнения набора инструкций фиксированное количество раз, пока не будет достигнут определенный результат или пока интерпретатор не достигнет конца итерируемой структуры данных (например, последнего элемента в массиве, карте или наборе, последнее свойство объекта или последний символ строки).
Циклы прерывают поток выполнения сценария «сверху вниз», перебирая набор операторов до тех пор, пока одно или несколько условий не будут выполнены или перестанут выполняться, в зависимости от синтаксиса, используемого для создания цикла. После завершения цикла выполнение продолжается до следующих за ним операторов. В следующем примере инструкции в теле цикла выполняются три раза, прежде чем интерпретатор продолжит работу:
let iterationCount = 0;
console.log( "Pre-loop." );
while( iterationCount < 3 ) {
iterationCount++;
console.log( "Loop iteration." );
}
console.log( "Continuing on." );
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Continuing on."
Если условия не могут быть выполнены во время выполнения цикла, цикл продолжается бесконечно. Эти бесконечные циклы являются распространенной ошибкой программирования, которая может привести к тому, что основной поток выполнения приостановится на неопределенный срок или даже приведет к сбою вкладки браузера.
Следующий пример выполняется до тех пор, пока логическое значение true
остается true
. Поскольку логические значения неизменяемы , это создает бесконечный цикл.
console.log( "Pre-loop." );
while( true ) {
console.log( "Loop iteration." );
}
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
…
Не оставляйте бесконечные циклы в рабочем коде. Если вы случайно создали его во время разработки, вы можете исправить это, закрыв вкладку браузера, в которой он работает, обновив код, чтобы цикл больше не был бесконечным, и снова открыв страницу.
while
Цикл while
создается с использованием ключевого слова while
, за которым следует пара совпадающих круглых скобок, содержащих условие, которое необходимо оценить. Если указанное условие первоначально оценивается как true
, выполняется оператор (или оператор блока ), следующий за этими круглыми скобками. В противном случае цикл никогда не запускается. После каждой итерации условие пересчитывается, и если оно по-прежнему true
, цикл повторяется.
let iterationCount = 0;
while( iterationCount < 3 ) {
iterationCount++;
console.log( `Loop ${ iterationCount }.` );
}
> "Loop 1."
> "Loop 2."
Если интерпретатор находит оператор continue
в цикле while
, он останавливает эту итерацию, повторно оценивает условие и продолжает цикл, если это возможно:
let iterationCount = 0;
while( iterationCount <= 5 ) {
iterationCount++;
if( iterationCount === 3 ) {
continue;
}
console.log( `Loop ${ iterationCount }.` );
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Loop 4."
> "Loop 5."
> "Loop ended."
Если интерпретатор находит оператор break
в цикле while
, эта итерация останавливается и условие не пересчитывается, позволяя интерпретатору двигаться дальше:
let iterationCount = 1;
while( iterationCount <= 5 ) {
if( iterationCount === 3 ) {
console.log(`Iteration skipped.``);`
break;
}
console.log( `Loop ${ iterationCount }.` );
iterationCount++;
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Iteration skipped.
> "Loop ended."
Вы можете использовать while
для итерации заданное количество раз, как показано в предыдущем примере, но наиболее распространенным вариантом использования while
является цикл неопределенной длины:
let randomize = () => Math.floor( Math.random() * 10 );
let randomNum = randomize();
while( randomNum !== 3 ){
console.log( `The number is not ${ randomNum }.` );
randomNum = randomize();
}
console.log( `The correct number, ${ randomNum }, was found.` );
> "The number is not 0."
> "The number is not 6."
> "The number is not 1."
> "The number is not 8."
> "The correct number, 3, was found."
do
… while
do
… while
— это вариант цикла while
, в котором условная оценка происходит в конце каждой итерации цикла. Это означает, что тело цикла всегда выполняется хотя бы один раз.
Чтобы создать цикл do
… while
, используйте ключевое слово do
, за которым следует оператор (или оператор блока ), который будет выполняться на каждой итерации цикла. Сразу после этого оператора добавьте while
и соответствующие круглые скобки, содержащие условие, которое нужно оценить. Когда это условие больше не принимает значение true
, цикл завершается.
let iterationCount = 1;
do {
console.log( `Loop ${ iterationCount }.` );
iterationCount++;
} while ( iterationCount < 3 );
> "Loop 1."
> "Loop 2."
> "Loop 3."
Как и в случае с циклом while
, наиболее распространенным вариантом использования do
… while
является цикл неопределенной длины:
let randomNum;
do {
randomNum = ( () => Math.floor( Math.random() * 10 ) )();
console.log( `Is the number ${ randomNum }?` );
} while ( randomNum !== 3 );
console.log( `Yes, ${ randomNum } was the correct number.` );
> "Is the number 9?"
> "Is the number 2?"
> "Is the number 8?"
> "Is the number 2?"
> "Is the number 3?"
> "Yes, 3 was the correct number."
for
Используйте циклы for
для перебора известной величины. В устаревших базах кода это часто использовалось для перебора элементов массива.
Чтобы создать цикл for
, используйте ключевое слово for
, за которым следует набор круглых скобок, которые принимают следующие три выражения по порядку и разделенные точкой с запятой:
- Выражение, которое будет оценено в начале цикла
- Условие, определяющее, следует ли продолжать цикл
- Выражение, которое будет выполняться по завершении каждого цикла.
После этих круглых скобок добавьте оператор (обычно оператор блока ), который будет выполняться во время цикла.
for( let i = 0; i < 3; i++ ) {
console.log( "This loop will run three times.")
}
Первое выражение инициализирует переменную, которая действует как счетчик. Это выражение вычисляется один раз перед первой итерацией цикла. Вы можете инициализировать эту переменную с помощью let
(или var
, исторически), как и любую другую переменную, и ее областью действия является тело цикла. Эти переменные могут иметь любой действительный идентификатор, но их часто называют i
, что означает «итерация» или «индекс». Кажется, это противоречит общепринятым рекомендациям по использованию предсказуемых имен идентификаторов , но соглашение достаточно устоялось, чтобы быть понятным другим разработчикам с первого взгляда. Поскольку индексированные коллекции имеют нулевой индекс , эти переменные почти всегда имеют начальное значение 0
.
Как и в других формах цикла, условие представляет собой выражение, определяющее, следует ли выполнять цикл. Чаще всего это используется для установки верхней границы счетчика итераций. Интерпретатор оценивает условие перед первым выполнением for
for. Если изначально условие не имеет значения true
, тело цикла не выполняется.
Последнее выражение выполняется в конце каждой итерации цикла. Обычно он используется для увеличения идентификатора на единицу.
Чаще всего циклы for
перебирают массивы в старых базах кода. В этих случаях условием продолжения цикла является число итераций, меньшее или равное длине итерируемого массива. Переменная, используемая для отслеживания текущего количества итераций, используется для поиска значения, связанного с этим индексом в массиве, позволяя действовать по порядку с каждым элементом массива:
var myArray = [ true, false, true ];
for( let i = 0; i <= myArray.length; i++ ) {
console.log( myArray[ i ] );
}
> true
> false
> true
Этот подход вышел из употребления в пользу более современных подходов к циклическому перебору итерируемых структур данных .
for
[…] of
[…]
Используйте циклы for
… of
… для перебора значений, хранящихся в повторяемой структуре данных , например массиве, наборе или карте.
Цикл for
… of
… использует ключевое слово for
, за которым следует набор круглых скобок, содержащих переменную, за которыми следует of
, а затем повторяется структура данных. Переменная может быть объявлением, выполненным здесь с использованием let
, const
или var
, переменной, объявленной ранее в текущей области видимости, свойством объекта или экземпляром деструктурирующего присваивания . Он содержит значение элемента, соответствующего текущей итерации цикла.
const myIterable = [ true, false, true ];
for( const myElement of myIterable ) {
console.log( myElement );
}
> true
> false
> true
В этом примере использование const
для myElement
работает, даже если myElement
получает новое значение на каждой итерации цикла. Это связано с тем, что переменные, объявленные с помощью let
или const
, относятся к оператору блока внутри цикла. Переменная инициализируется в начале каждой итерации и удаляется в конце этой итерации.
for
... in
...
Используйте циклы for
… in
… для перебора перечислимых свойств объекта, включая перечислимые наследуемые свойства. Как и в случае цикла for
… of
…, цикл for
… in
… использует ключевое слово for
, за которым следует набор круглых скобок, содержащих переменную, содержащую значение ключа свойства, соответствующего текущей итерации цикла. За этой переменной следует ключевое слово in
, а затем объект, по которому выполняется итерация:
const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
console.log( myKey );
}
> "myProperty"
> "mySecondProperty"
Опять же, несмотря на то, что значение myKey
меняется с каждой итерацией цикла, вы можете использовать const
без ошибок, поскольку переменная фактически отбрасывается в конце каждой итерации, а затем воссоздается в начале.
Значение, связанное с каждым ключом свойства, недоступно напрямую для синтаксиса for
… in
…. Однако, поскольку цикл имеет доступ к ключу свойства на каждой итерации, вы можете использовать этот ключ для «поиска» его значения:
const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
const myValue = myObject[ myKey ];
console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "mySecondProperty : false"
Свойства, унаследованные от встроенных конструкторов, являются неперечисляемыми. Это означает, что for
… in
… не перебирает свойства, унаследованные от конструктора Object
. Однако любые перечислимые свойства в цепочке прототипов объекта включаются:
const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
myProperty: {
value: true,
enumerable: true
}
});
for ( const myKey in myObject ) {
const myValue = myObject[ myKey ];
console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "protoProperty : true"
JavaScript предоставляет встроенные методы для определения того, является ли свойство прямым свойством объекта, а не свойством в цепочке прототипов объекта: современные Object.hasOwn()
и устаревшие Object.prototype.hasOwnProperty()
. Эти методы оценивают, наследуется ли указанное свойство (или необъявлено), возвращая true
только для непосредственных свойств указанного объекта:
const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
myProperty: {
value: true,
enumerable: true
}
});
for ( const myKey in myObject ) {
const myValue = myObject[ myKey ];
if ( Object.hasOwn( myObject, myKey ) ) {
console.log( `${ myKey } : ${ myValue }` );
}
}
> "myProperty : true"
Существует также три статических метода, каждый из которых возвращает массив, состоящий из перечислимых ключей объекта ( Object.keys()
), значений ( Object.values()
) или пар ключ-значение ( Object.entries()
):
const myObject = { "myProperty" : true, "mySecondProperty" : false };
Object.keys( myObject );
> Array [ "myProperty", "mySecondProperty" ]
Это позволяет вам перебирать ключи, значения или пары ключ-значение объекта (с использованием деструктурирующего присваивания ) без включения свойств, принадлежащих прототипу этого объекта:
const myPrototype = { "protoProperty" : "Non-enumerable property value." };
const myObject = Object.create( myPrototype, {
myProperty: {
value: "Enumerable property value.",
enumerable: true
}
});
for ( const propKey of Object.keys( myObject ) ) {
console.log( propKey );
}
> "myProperty"
for ( const propValue of Object.values( myObject ) ) {
console.log( propValue );
}
> "Enumerable property value."
for ( const [ propKey, propValue ] of Object.entries( myObject ) ) {
console.log( `${ propKey } : ${ propValue }` );
}
> "myProperty : Enumerable property value."
forEach()
Методы forEach()
, предоставляемые конструкторами Array , Map , Set и NodeList, предоставляют полезное сокращение для перебора структуры данных в контексте функции обратного вызова. В отличие от других форм цикла, цикл, созданный с помощью любого метода forEach()
не может быть прерван с помощью break
или continue
.
forEach
— это метод, принадлежащий прототипу каждой структуры данных. Каждый метод forEach
ожидает функцию обратного вызова в качестве аргумента, хотя они немного различаются с точки зрения аргументов, включаемых при вызове этой функции. Второй необязательный аргумент указывает значение this
, которое будет использоваться в качестве контекста вызова функции обратного вызова.
Функция обратного вызова, используемая с Array.forEach
предоставляет параметры, содержащие значение текущего элемента, индекс текущего элемента и массив, для которого был вызван метод forEach
:
const myArray = [ true, false ];
myArray.forEach( ( myElement, i, originalArray ) => {
console.log( i, myElement, originalArray );
});
> 0 true Array(3) [ true, false ]
> 1 false Array(3) [ true, false ]
Функция обратного вызова, используемая с Map.forEach
предоставляет параметры, содержащие значение, связанное с текущим элементом, ключ, связанный с текущим элементом, и карту, на которой был вызван метод forEach
:
const myMap = new Map([
['myKey', true],
['mySecondKey', false ],
]);
myMap.forEach( ( myValue, myKey, originalMap ) => {
console.log( myValue, myKey, originalMap );
});
> true "myKey" Map { myKey → true, mySecondKey → false }
> false "mySecondKey" Map { myKey → true, mySecondKey → false }
Обратный вызов Set.forEach
включает аналогичные параметры. Поскольку Set не имеет индексов или ключей, отличных от значений, вместо этого второй аргумент предоставляет избыточное, игнорируемое значение, строго для обеспечения согласованности синтаксиса с другими методами forEach
.
const mySet = new Set([ true, false ]);
mySet.forEach( ( myValue, myKey, originalSet ) => {
console.log( myValue, myKey, originalSet );
});
> true true Set [ true, false ]
> false false Set [ true, false ]
Итераторы
Итерируемый объект — это любая структура данных, состоящая из отдельных элементов, которые можно перебирать, используя описанные ранее подходы. Итератор — это итерируемый объект, который следует протоколу итератора . Это означает, что он должен реализовать метод next()
, который проходит через содержащиеся в нем элементы по одному, каждый раз при вызове этого метода, возвращая объект для каждого последовательного элемента в определенном формат.
Встроенные итерируемые структуры данных JavaScript (такие как Array , Map и Set ) сами по себе не являются итераторами, но все они наследуют метод iterator
, доступный с помощью широко известного символа @@iterator
, который возвращает итератор. объект, созданный из итерируемой структуры данных:
const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();
myIterable;
> (3) [1, 2, 3]
myIterator;
> Array Iterator {}
Вызов метода next()
для итератора проходит по элементам, которые он содержит, по одному, при этом каждый вызов возвращает объект, содержащий два свойства: value
, которое содержит значение текущего элемента, и done
, логическое значение, которое сообщает нам, если итератор передал последний элемент в структуре данных. Значение done
true
только тогда, когда вызов next()
приводит к попытке доступа к элементу, находящемуся за последним элементом итератора.
const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();
myIterator.next();
> Object { value: 1, done: false }
myIterator.next();
> Object { value: 2, done: false }
myIterator.next();
> Object { value: 3, done: false }
myIterator.next();
> Object { value: undefined, done: true }
Функции генератора
Используйте ключевое слово function*
(обратите внимание на звездочку), чтобы объявить функцию-генератор или определить выражение функции-генератора:
function* myGeneratorFunction() { };
Подобно итераторам , функции-генераторы поддерживают состояние. Вызов функции-генератора возвращает новый объект Generator, но не выполняет сразу код в теле функции:
function* myGeneratorFunction() {
console.log( "Generator function body ")
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject;
> Generator { }
typeof myGeneratorObject;
> "object"
Объекты-генераторы следуют протоколу итераторов . Значение, возвращаемое каждым вызовом next()
функции-генератора, определяется выражением yield
, которое приостанавливает выполнение функции-генератора и возвращает значение выражения, содержащего ключевое слово yield
. Последующие вызовы next()
продолжают выполнение функции, делая паузу на следующем выражении yield
и возвращая связанное значение.
function* myGeneratorFunction() {
yield "My first yielded value.";
yield "My second yielded value.";
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: "My first yielded value.", done: false }
myGeneratorObject.next();
> Object { value: "My second yielded value.", done: false }
Когда next()
вызывается после того, как никакие дополнительные значения не указаны с использованием yield
, return
или throw
(в случае ошибки), выполняется оставшаяся часть тела функции, а возвращаемый объект имеет value
undefined
и свойство done
. true
:
function* myGeneratorFunction() {
console.log( "Start of the generator function." );
yield "First";
console.log( "Second part of the generator function." );
yield "Second";
console.log( "Third part of the generator function." );
yield "Third";
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> "Start of the generator function."
> Object { value: "First", done: false }
myGeneratorObject.next();
> "Second part of the generator function."
> Object { value: "Second", done: false }
myGeneratorObject.next();
> "Third part of the generator function."
> Object { value: "Third", done: false }
myGeneratorObject.next();
> Object { value: undefined, done: true }
Используйте next()
только для объекта, который возвращает функция-генератор, а не для самой функции-генератора. В противном случае каждый вызов функции-генератора создает новый объект-генератор:
function* myGeneratorFunction() {
yield "First";
yield "Second";
};
myGeneratorFunction().next();
> Object { value: "First", done: false }
myGeneratorFunction().next();
> Object { value: "First", done: false }
Как и любая функция, функция-генератор останавливается, когда встречает ключевое слово return
. Затем он возвращает объект в вызывающий контекст, который содержит возвращаемое значение и свойство done
со значением true
.
function* myGeneratorFunction() {
yield 1;
yield 2;
return 3;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next().done;
> Object { value: 1, done: false }
myGeneratorObject.next().done;
> Object { value: 2, done: false }
myGeneratorObject.next();
> Object { value: 3, done: true }
Выражение yield
может принимать на себя часть семантики идентификатора, обеспечивая двустороннюю «связь» с приостановленной частью функции-генератора и обратно. Когда значение передается методу генератора next()
в качестве аргумента, оно заменяет значение, связанное с предыдущим приостановленным выражением yield
:
function* myGeneratorFunction() {
const firstYield = yield;
yield firstYield + 10;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: undefined, done: false }
myGeneratorObject.next( 5 );
> Object { value: 15, done: false }
Имейте в виду, что это заменяет все выражение, связанное с предыдущим yield
, а не просто переназначает значение предыдущего yield
на значение, указанное в next()
:
function* myGeneratorFunction() {
const firstYield = yield;
const secondYield = yield firstYield + 100;
yield secondYield + 10;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: undefined, done: false }
myGeneratorObject.next( 10 ); // Can be thought of as changing the value of the `firstYield` variable to `10
> Object { value: 110, done: false }
myGeneratorObject.next( 20 ); // Can be thought of as changing the value of the `secondYield` variable to `20`, _not_ `20 + 100;`
> Object { value: 30, done: false }
Любой аргумент, передаваемый при первом вызове next()
игнорируется, поскольку не существует предыдущего выражения yield
, которое могло бы принять это значение. Как и в случае с любой другой функцией, аргументы, передаваемые при первоначальном вызове функции-генератора, доступны во всем теле функции-генератора:
function* myGeneratorFunction( startingValue ) {
let newValue = yield startingValue + 1;
newValue = yield newValue + 10;
yield startingValue + 20;
};
const myGeneratorObject = myGeneratorFunction( 2 );
myGeneratorObject.next( 1 );
> Object { value: 3, done: false }
myGeneratorObject.next( 5 );
> Object { value: 15, done: false }
myGeneratorObject.next( 10 );
Object { value: 22, done: false }
Оператор yield*
(обратите внимание на звездочку) используется с итерируемым объектом, например с другой функцией-генератором, для перебора и получения каждого значения, возвращаемого его операндом:
function* mySecondaryGenerator() {
yield 2;
yield 3;
}
function* myGenerator() {
yield 1;
yield* mySecondaryGenerator();
yield 4;
return 5;
}
const myIterator = myGenerator();
myIterator.next();
> Object { value: 1, done: false }
myIterator.next();
> Object { value: 2, done: false }
myIterator.next();
> Object { value: 3, done: false }
myIterator.next();
> Object { value: 4, done: false }
myIterator.next();
> Object { value: 5, done: true }
Асинхронный JavaScript
Хотя JavaScript по своей сути синхронен в исполнении, существуют механизмы, которые позволяют разработчикам использовать цикл событий для выполнения асинхронных задач.
Обещания
Обещание — это заполнитель для значения, которое неизвестно на момент создания обещания. Это контейнер, который определяет асинхронную операцию, условия, согласно которым операция считается успешной или неудачной, действия, которые необходимо предпринять в любом случае, и получаемое в результате значение.
Создайте экземпляр Promise, используя оператор new
со встроенной функцией конструктора Promise
. Этот конструктор принимает в качестве аргумента функцию, называемую исполнителем . Эта функция-исполнитель обычно используется для выполнения одного или нескольких асинхронных действий, а затем определяет условия, при которых обещание должно считаться успешно выполненным или отклоненным. Обещание определяется как ожидающее , пока выполняется функция исполнителя. После завершения работы исполнителя обещание считается выполненным (или разрешенным в некоторых источниках документации), если функция исполнителя и выполняемое им асинхронное действие завершаются успешно, и отклоняется, если функция исполнителя обнаруживает ошибку или выполняемое асинхронное действие завершается неудачно. . После того, как обещание выполнено или отклонено, оно считается выполненным .
const myPromise = new Promise( () => { });
Конструктор вызывает функцию-исполнитель с двумя аргументами. Эти аргументы представляют собой функции, которые позволяют вам вручную выполнить или отклонить обещание:
const myPromise = new Promise( ( fulfill, reject ) => { });
Функции, используемые для выполнения или отклонения обещания, вызываются с результирующим значением обещания в качестве аргумента (обычно это ошибка для отклонения):
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = true;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was successful." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 10000);
});
myPromise;
> Promise { <state>: "pending" }
myPromise;
> Promise { <state>: "fulfilled", <value>: "This Promise was successful." }
Цепочка обещаний
С получившимся объектом Promise можно работать с помощью методов then()
, catch()
и finally()
, унаследованных от конструктора Promise. Каждый из этих методов возвращает промис, к которому можно немедленно применить then()
, catch()
или finally()
снова, что позволяет вам объединить полученные промисы.
then()
предоставляет в качестве аргументов две функции обратного вызова. Используйте первый, чтобы выполнить полученное Обещание, а второй, чтобы отклонить его. Оба метода принимают один аргумент, который придает результирующему обещанию его значение.
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = true;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was fulfilled." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 100);
});
myPromise.then( successfulResult => console.log( successfulResult ), failedResult => console.error( failedResult ) );
> "This Promise was successful."
Вы также можете использовать then()
для обработки только выполненного состояния и catch
для обработки отклоненного состояния. Вызовите catch
с одним аргументом, содержащим значение, указанное в методе отклонения обещания:
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = false;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was fulfilled." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 100);
});
myPromise
.then( fulfilledResult => console.log(fulfilledResult ) )
.catch( rejectedResult => console.log( rejectedResult ) )
.finally( () => console.log( "The Promise has settled." ) );
> "Error: This Promise has been rejected."
> "The Promise has settled."
В отличие от then
и catch
, которые позволяют функции-обработчику запускаться при выполнении или отклонении обещания, функция, передаваемая в качестве аргумента finally
, вызывается независимо от того, было ли обещание выполнено или отклонено. Функция-обработчик вызывается без аргументов, поскольку она не предназначена для работы со значениями, переданными из обещания, а только для выполнения кода после завершения обещания.
Параллелизм
Конструктор Promise предоставляет четыре метода для работы с несколькими связанными Promise, используя итерацию, содержащую объекты Promise. Каждый из этих методов возвращает обещание, которое выполняется или отклоняется в зависимости от состояния переданных ему обещаний. Например, Promise.all()
создает промис, который выполняется только в том случае, если все промисы, переданные этому методу, выполняются:
const firstPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const secondPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const thirdPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const failedPromise = new Promise( ( fulfill, reject ) => reject( "Failed.") );
const successfulPromises = [ firstPromise, secondPromise, thirdPromise ];
const oneFailedPromise = [ failedPromise, ...successfulPromises ];
Promise.all( successfulPromises )
.then( ( allValues ) => {
console.log( allValues );
})
.catch( ( failValue ) => {
console.error( failValue );
});
> Array(3) [ "Successful. ", "Successful. ", "Successful. " ]
Promise.all( oneFailedPromise )
.then( ( allValues ) => {
console.log( allValues );
})
.catch( ( failValue ) => {
console.error( failValue );
});
> "Failed."
Методы параллелизма Promise следующие:
-
Promise.all()
- Выполняется только в том случае, если все предоставленные обещания выполнены.
-
Promise.any()
- Выполняется, если выполнено какое-либо из предоставленных обещаний, и отклонено, только если отклонены все обещания.
-
Promise.allSettled()
- Выполняется, когда Обещания исполнены, независимо от их результата.
-
Promise.race()
- Отклонено или выполнено на основании результата первого обещания, которое будет выполнено, игнорируя все обещания, выполненные позже.
async
/ await
Когда вы используете ключевое слово async
перед объявлением функции или выражением функции , любое значение, возвращаемое функцией, возвращается как выполненное обещание, содержащее это значение. Это позволяет запускать асинхронные операции и управлять ими, используя те же рабочие процессы, что и при синхронной разработке.
async function myFunction() {
return "This is my returned value.";
}
myFunction().then( myReturnedValue => console.log( myReturnedValue ) );
> "This is my returned value."
Выражение await
приостанавливает выполнение асинхронной функции, пока выполняется связанное с ней обещание. После того, как обещание выполнено, значение выражения await
является выполненным или отклоненным значением обещания.
async function myFunction() {
const myPromise = new Promise( ( fulfill, reject ) => { setTimeout( () => fulfill( "Successful. "), 5000 ); });
const myPromisedResult = await myPromise;
return myPromisedResult;
}
myFunction()
.then( myResult => console.log( myResult ) )
.catch( myFailedResult => console.error( myFailedResult ) );
> "Successful."
Любое значение, не являющееся обещанием, включенное в выражение await
, возвращается как выполненное обещание:
async function myFunction() {
const myPromisedResult = await "String value.";
return myPromisedResult;
}
myFunction()
.then( myResult => console.log( myResult ) )
.catch( myFailedResult => console.error( myFailedResult ) );
> "String value."
Проверьте свое понимание
Какой тип цикла вы используете для перебора известной величины?
for
while
do...while