控制流

控制流是 JavaScript 解释器执行语句的顺序。如果脚本不包含更改其流程的语句,则会从头到尾逐行执行。控制结构用于确定是否根据一组定义的条件执行一组语句、重复执行一组语句或中断一系列语句。

条件语句

条件语句确定是否应根据一个或多个条件执行代码。如果关联的条件(或条件集)的计算结果为 true,则条件语句会执行其包含的代码。否则,将跳过该代码。

ifelse

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."

switchcase

使用 switch 语句将表达式的值与使用一个或多个 case 关键字定义的潜在值列表进行比较。此语法不寻常,因为它来自 JavaScript 的一些最早的设计决策。switchcase 语法使用 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 关键字结束每个 case,这会立即停止 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,可能会导致意外结果。要解决此问题,请使用 break 结束您的 default 语句,或将其放在 case 列表的末尾。

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 子句不需要块语句来对多个语句进行分组,因此 casedefault 子句本身不会创建词法作用域

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."
> "Loop 3."

如果解释器在 while 循环中找到 continue 语句,它会停止该迭代,重新评估条件,并在可能的情况下继续循环

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."

如果解释器在 while 循环中找到 break 语句,则该迭代会停止,并且不会重新评估条件,从而使解释器继续执行

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."

dowhile

dowhilewhile 循环的变体,其中条件评估发生在循环的每次迭代的末尾。这意味着循环的主体始终至少执行一次。

要创建 dowhile 循环,请使用 do 关键字,后跟要在循环的每次迭代中执行的语句(或块语句)。紧跟在该语句之后,添加 while 和包含要评估的条件的匹配括号。当此条件不再计算结果为 true 时,循环结束。

let iterationCount = 1;
do {
  console.log( `Loop ${ iterationCount }.` );
  iterationCount++;
} while ( iterationCount < 3 );
> "Loop 1."
> "Loop 2."
> "Loop 3."

while 循环一样,dowhile 最常见的用例是长度不确定的循环

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 关键字,后跟一组括号,该括号按顺序接受以下三个表达式,并用分号分隔

  1. 循环开始时要评估的表达式
  2. 确定循环是否应继续的条件
  3. 在每次循环结束时要执行的表达式

在这些括号之后,添加要在循环期间执行的语句(通常为块语句)。

for( let i = 0; i < 3; i++ ) {
  console.log( "This loop will run three times.")
}

第一个表达式初始化一个充当计数器的变量。此表达式在循环的第一次迭代之前评估一次。您可以像任何其他变量一样使用 let(或历史上的 var)初始化此变量,并且其作用域是循环的主体。这些变量可以具有任何有效的标识符,但它们通常被称为 i,表示“迭代”或“索引”。这似乎与已建立的可预测标识符名称的最佳实践相矛盾,但该约定已足够成熟,其他开发人员可以一目了然地理解。由于索引集合从零开始索引,因此这些变量几乎总是具有初始值 0

与其他形式的循环一样,条件是一个表达式,用于确定是否应执行循环。这最常用于设置迭代计数器的上限。解释器在第一次执行 for 循环之前评估条件。如果条件最初未计算结果为 true,则不会执行循环的主体。

最终表达式在每次循环迭代结束时执行。它通常用于将标识符递增 1。

您最常会在较旧的代码库中看到 for 循环迭代数组。在这些情况下,为继续循环指定的条件是迭代计数小于或等于正在迭代的数组的长度。用于跟踪当前迭代计数的变量用于查找与数组中该索引关联的值,从而允许按顺序处理数组中的每个元素

var myArray = [ true, false, true ];
for( let i = 0; i < myArray.length; i++ ) {
  console.log( myArray[ i ] );
}
> true
> false
> true

这种方法已不再使用,取而代之的是更现代的方法来循环可迭代数据结构

for […] of […]

使用 forof… 循环迭代存储在可迭代数据结构(例如数组、集合或映射)中的值。

forof… 循环使用 for 关键字,后跟一组括号,其中包含一个变量,后跟 of,然后是要迭代的数据结构。该变量可以是此处使用 letconstvar 执行的声明、当前作用域内先前声明的变量、对象属性或解构赋值的实例。它包含与循环的当前迭代相对应的元素的值。

const myIterable = [ true, false, true ];
for( const myElement of myIterable ) {
  console.log( myElement );
}
> true
> false
> true

在此示例中,即使在循环的每次迭代中都为 myElement 赋予新值,使用 const 来表示 myElement 也能正常工作。这是因为使用 letconst 声明的变量的作用域限定为循环内的块语句。变量在每次迭代开始时初始化,并在该迭代结束时删除。

forin

使用 forin… 循环迭代对象的枚举属性,包括枚举的继承属性。与 forof… 循环一样,forin… 循环使用 for 关键字,后跟一组括号,其中包含一个变量,该变量包含与循环的当前迭代相对应的属性键的值。此变量后跟 in 关键字,然后是要迭代的对象

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
  console.log( myKey );
}
> "myProperty"
> "mySecondProperty"

同样,尽管 myKey 的值在循环的每次迭代中都会更改,但您可以使用 const 而不会出错,因为该变量在每次迭代结束时都会被有效地丢弃,然后在开始时重新创建。

与每个属性键关联的值不能直接用于 forin… 语法。但是,由于循环在每次迭代中都可以访问属性键,因此您可以使用该键来“查找”其值

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
  const myValue = myObject[ myKey ];
  console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "mySecondProperty : false"

从内置构造函数继承的属性是不可枚举的,这意味着 forin… 不会迭代从 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()

ArrayMapSet 和 NodeList 构造函数提供的 forEach() 方法为在回调函数的上下文中迭代数据结构提供了一个有用的简写形式。与其他形式的循环不同,使用任何 forEach() 方法创建的循环都不能使用 breakcontinue 中断。

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 方法的 Map 的参数

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 的内置可迭代数据结构(例如 ArrayMapSet)本身不是迭代器,但它们都继承了一个 iterator 方法,可以使用 @@iterator 众所周知的符号访问该方法,该符号返回从可迭代数据结构创建的迭代器对象

const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();

myIterable;
> (3) [1, 2, 3]

myIterator;
> Array Iterator {}

在迭代器上调用 next() 方法会一次步进它包含的元素,每次调用都会返回一个包含两个属性的对象:value,其中包含当前元素的值,以及 done,一个布尔值,告诉我们迭代器是否已传递数据结构中的最后一个元素。done 的值仅在调用 next() 导致尝试访问迭代器中最后一个元素之后的元素时才为 true

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"

Generator 对象遵循迭代器协议。每次调用生成器函数上的 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 }

当在使用 yieldreturnthrow(在发生错误时)未指定进一步的值后调用 next() 时,将执行函数主体的其余部分,并且返回的 Object 的 valueundefineddone 属性为 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 }

仅在生成器函数返回的 Object 上使用 next(),而不是在生成器函数本身上使用。否则,每次调用生成器函数都会创建一个新的生成器 Object

function* myGeneratorFunction() {
  yield "First";
  yield "Second";
};

myGeneratorFunction().next();
> Object { value: "First", done: false }

myGeneratorFunction().next();
> Object { value: "First", done: false }

与任何函数一样,当生成器函数遇到 return 关键字时,它会停止。然后,它会向调用上下文返回一个 Object,其中包含返回值和一个值为 truedone 属性。

function* myGeneratorFunction() {
  yield 1;
  yield 2;
  return 3;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next();
> Object { value: 1, done: false }

myGeneratorObject.next();
> 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

Promise 是在创建 promise 时未知的值的占位符。它是一个容器,用于指示异步操作、操作被视为成功或失败的条款、在任一情况下要采取的操作以及产生的值。

使用带有内置 Promise 构造函数函数的 new 运算符创建 Promise 实例。此构造函数接受一个名为执行器的函数作为参数。该执行器函数通常用于执行一个或多个异步操作,然后指示应将 Promise 视为成功履行还是拒绝的条款。在执行器函数运行时,Promise 被定义为待处理状态。执行器完成后,如果执行器函数及其执行的异步操作成功完成,则 Promise 被视为已履行(或在某些文档来源中为已解决),如果执行器函数遇到错误或正在执行的异步操作失败,则 Promise 被视为已拒绝。在 Promise 被履行或拒绝后,它被视为已敲定

const myPromise = new Promise( () => { });

构造函数使用两个参数调用执行器函数。这些参数是允许您手动履行或拒绝 Promise 的函数

const  myPromise = new Promise( ( fulfill, reject ) => { });

用于履行或拒绝 Promise 的函数使用 Promise 的结果值(通常是拒绝的错误)作为参数调用

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 链

可以使用从 Promise 构造函数继承的 then()catch()finally() 方法对生成的 Promise 对象执行操作。这些方法中的每一种都返回一个 Promise,可以使用 then()catch()finally() 再次立即对其执行操作,从而允许您链接生成的 Promise。

then() 提供两个回调函数作为参数。使用第一个来履行生成的 Promise,使用第二个来拒绝它。这两种方法都接受一个参数,该参数为生成的 Promise 提供其值。

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 处理已拒绝的状态。使用包含 Promise 的拒绝方法中提供的值的单个参数调用 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."

thencatch(它们允许在 Promise 被履行或拒绝时运行处理函数)不同,传递给 finally 方法的函数的参数无论 Promise 是履行还是拒绝都会被调用。调用处理函数时不带任何参数,因为它不打算处理从 Promise 传递的值,而仅在 Promise 完成后执行代码。

并发

Promise 构造函数提供了四种方法来处理多个相关的 Promise,使用包含 Promise 对象的可迭代对象。这些方法各自返回一个 Promise,该 Promise 会根据传递给它的 Promise 的状态变为 fulfilled 或 rejected。例如,Promise.all() 创建一个 Promise,只有当传递给该方法的每个 Promise 都变为 fulfilled 时,该 Promise 才会变为 fulfilled

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 都变为 fulfilled 时,才会变为 fulfilled。
Promise.any()
如果提供的 Promise 中有任何一个变为 fulfilled,则变为 fulfilled;仅当所有 Promise 都变为 rejected 时,才会变为 rejected。
Promise.allSettled()
当 Promise 已 settled(无论其结果如何)时,变为 fulfilled。
Promise.race()
根据第一个 settled 的 Promise 的结果变为 rejected 或 fulfilled,忽略所有稍后 settled 的 Promise。

async/await

当您在函数声明函数表达式之前使用 async 关键字时,该函数返回的任何值都将作为包含该值的 fulfilled Promise 返回。这使您可以使用与同步开发相同的工作流程来运行和管理异步操作。

async function myFunction() {
  return "This is my returned value.";
}

myFunction().then( myReturnedValue => console.log( myReturnedValue ) );
> "This is my returned value."

await 表达式会暂停异步函数的执行,直到关联的 Promise settled。Promise settled 后,await 表达式的值是 Promise 的 fulfilled 或 rejected 值。

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 表达式中的非 Promise 值都将作为 fulfilled Promise 返回

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