控制流程

控制流程是 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."

如果沒有與條件值相符的 caseswitch 會選取 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 子句不需要 分組的區塊陳述式 多個陳述式,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."

如果解譯器在 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."

do……while

do...whilewhile 迴圈的變化版本,其中有條件式 評估會在每次迴圈疊代的「結尾」發生。也就是說, 迴圈主體一律至少執行一次

如要建立 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 關鍵字,後面加上一組括號 可接受以下三個運算式,依序使用 分號:

  1. 迴圈開始時評估的運算式
  2. 決定迴圈是否應繼續的條件
  3. 在每個迴圈結束時執行的運算式

然後在括號後方加上陳述式 (通常是 封鎖陳述式) 會在迴圈中執行。

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

第一個運算式會初始化一個變數,做為計數器。這個 運算式會在第一次疊代之前,評估一次。你可以 比照任何其他做法,使用 let (或以往的 var) 初始化這個變數 變數,且範圍是迴圈的主體。這些變數可以包含 有效 ID,但經常稱為 i,用於「疊代」或「索引」 這似乎與既定成果相牴觸 可預測 ID 名稱的最佳做法 但慣例已經建立好,可以讓其他開發人員 內容。由於已建立索引的集合不會編入索引, 這些變數的初始值為 0

與其他形式的迴圈相同,條件就是決定 是否應執行迴圈通常用來設定 疊代計數器的 bound。解譯器會先評估 第一次執行 for 迴圈。如果條件起初並未執行 評估為 true,則不會執行迴圈主體。

每次疊代時,都會在迴圈結束時執行最終運算式。 通常用於將 ID 遞增 1。

您最常看到 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,接著系統就會疊代資料結構 。變數可以是這裡使用 letconstvar,這是之前在目前範圍內宣告的變數,物件 或是其執行個體 解構作業。 其中包含與目前疊代相對應的元素值 迴圈結構

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

在此範例中,針對 myElement 使用 const 即使 myElement 是 並在每次迴圈疊代時提供新的值這是因為變數 使用 letconst 宣告的範圍限定在 迴圈這個變數會在每次疊代時初始化,並在 我們再決定疊代的部分

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 提供的內建方法可判斷屬性是否為 物件的直接屬性,而非物件原型上的屬性 鏈結:modern 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() 方法。 地圖設定、 NodeList 建構函式很適合用來疊代資料 回呼函式採用的結構。不同於其他形式的迴圈 無法透過任何 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 是一個布林值,用來指出疊代器是否已在 資料結構只有在呼叫 next() 時,done 的值才會是 true 會導致系統嘗試存取 iterator。

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 }

在未使用 yield 指定其他值後呼叫 next() 時, returnthrow (發生錯誤時),函式的其餘部分 會執行主體,而傳回的物件的 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 }

請只在產生器函式傳回的物件上使用 next(),不要使用 編碼器-解碼器函式本身否則,每次對產生器函式的呼叫 建立新的產生器物件:

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

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

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

與任何函式一樣,產生器函式會在遇到 return 時暫停 字詞。然後它會將物件傳回至叫用的結構定義,其中包含 傳回的值以及值為 truedone 屬性。

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 運算式可採用 ID 的部分語意 允許雙向的「溝通」從三個可解決的廣告請求中 產生器的功能當值傳遞至產生器的 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 的值重新指派給yieldnext() 中指定的值:

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 是一個預留位置,但該值在保證為 已建立。這個容器可以指定非同步作業 檢查作業是否成功或失敗 陣列的值,再加上產生的值。

使用 new 運算子和內建 Promise 建立 Promise 執行個體 建構函式函數。這個建構函式接受名為「執行器」的函式 做為引數這個執行程式函式通常用於 非同步動作,然後指定 Promise 的 視為成功完成或遭到拒絕Promise 的定義為「待處理」 執行期間。執行程式完成後,會出現一個 Promise 表示屬於「可填入內容」 (在部分說明文件來源中,則是「已解決」) 執行函式所執行的非同步動作 如果執行工具函式發生錯誤,則成功,或 rejected。 執行的非同步動作失敗Promise 實現或 系統會視為「已設定」

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

建構函式會使用兩個引數呼叫執行程式函式。這些引數 可用來手動執行或拒絕 Promise:

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

用於執行或拒絕 Promise 的函式會呼叫 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() 和 繼承自 Promise 建構函式的 finally() 方法。每個 方法會傳回 Promise,立即使用 then() 採取行動。 catch(),或再次使用 finally(),讓您「鏈結」產生的 Promise。

then() 提供兩個回呼函式做為引數。使用第一個項目完成 產生的 Promise,並以第二個方式拒絕該 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 則可 處理遭拒狀態呼叫 catch,並使用包含 Promise 拒絕方法中提供的值:

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.all() 可建立 Promise 請依照所有 Promise 傳遞至該方法才執行以下動作:

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 都完全符合的情況下,才算完成。
Promise.any()
若提供的任一 Promise 符合,且僅遭拒,就會自動填入 如果所有 Promise 都遭到拒絕
Promise.allSettled()
無論結果為何,在 Promise 解決後就完成。
Promise.race()
根據第一份承諾書的結果拒絕或完成。 忽略所有稍後解決的 Promise。

async/await

當您在函式宣告之前使用 async 關鍵字 或函式運算式,任何 函式傳回的值會視為完成的 Promise, 值。讓您以相同的方式執行及管理非同步作業 以及同步開發工作流程

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

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

await 運算式會暫停執行非同步函式, 相關聯的 Promise 已設定完成設定好 Promise 之後, await 運算式是 Promise 的執行或遭拒值。

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 值都會傳回為 完成承諾:

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