ฟังก์ชัน Async พร้อม + =


63

let x = 0;

async function test() {
    x += await 5;
    console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

ค่าของxลงทะเบียนเป็นและ1 5คำถามของฉันคือ: ทำไมมูลค่าของการx 5เข้าสู่ระบบที่สอง?

หากtestมีการดำเนินการหลังจากx += 1(เพราะมันเป็นฟังก์ชั่น async) แล้วค่าของ x 1 ตามเวลาที่testจะดำเนินการดังนั้นควรจะทำให้ค่าของx += await 5x 6


1
คุณต้องทราบความแตกต่างระหว่างและawait (x += 5) x += await 5
Singhi John

คำตอบ:


60

TL; DR:เพราะ+=อ่านxมาก่อน แต่เขียนหลังจากเปลี่ยนไปเนื่องจากawaitคำสำคัญในตัวถูกดำเนินการตัวที่สอง (ด้านขวามือ)


asyncฟังก์ชั่นทำงานพร้อมกันเมื่อพวกเขาเรียกจนawaitคำสั่งแรก

ดังนั้นหากคุณลบออกawaitมันจะทำงานเหมือนฟังก์ชั่นปกติ (ยกเว้นว่ามันยังคงส่งคืนสัญญา)

ในกรณีนั้นคุณจะได้รับ5และ6อยู่ในคอนโซล:

let x = 0;

async function test() {
  x += 5;
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

ครั้งแรกที่awaitหยุดการทำงานแบบซิงโครนัสแม้ว่าอาร์กิวเมนต์จะพร้อมใช้งานพร้อมกันดังนั้นสิ่งต่อไปนี้จะกลับมา1และ6ตามที่คุณคาดหวัง:

let x = 0;

async function test() {
  // Enter asynchrony
  await 0;

  x += 5;
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

อย่างไรก็ตามกรณีของคุณซับซ้อนกว่าเล็กน้อย

คุณได้ใส่ภายในนิพจน์ที่ใช้await+=

คุณอาจจะรู้ว่าใน JS เป็นเหมือนx += y x = (x + y)ฉันจะใช้แบบฟอร์มหลังเพื่อความเข้าใจที่ดีขึ้น:

let x = 0;

async function test() {
  x = (x + await 5);
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

เมื่อล่ามมาถึงบรรทัดนี้ ...

x = (x + await 5);

... มันเริ่มประเมินมันแล้วมันก็เปลี่ยนเป็น ...

x = (0 + await 5);

... จากนั้นจะถึงawaitและหยุด

รหัสหลังจากการเรียกใช้ฟังก์ชันเริ่มทำงานและแก้ไขค่าxแล้วบันทึก

x1อยู่ในขณะนี้

จากนั้นหลังจากที่ออกจากสคริปต์หลักแล้วล่ามจะกลับไปที่testฟังก์ชันที่หยุดชั่วคราวและทำการประเมินบรรทัดนั้นต่อ:

x = (0 + 5);

และเนื่องจากค่าของแทนแล้วมันยังคงอยู่x0

สุดท้ายล่ามไม่นอกจากนี้ร้านค้า5ไปxและบันทึกมัน

คุณสามารถตรวจสอบพฤติกรรมนี้ได้โดยการเข้าสู่ระบบภายในคุณสมบัติวัตถุ getter / setter (ในตัวอย่างนี้y.zแสดงค่าของx:

let x = 0;
const y = {
  get z() {
    console.log('get x :', x);
    return x;
  },
  set z(value) {
    console.log('set x =', value);
    x = value;
  }
};

async function test() {
  console.log('inside async function');
  y.z += await 5;
  console.log('x :', x);
}

test();
console.log('main script');
y.z += 1;
console.log('x :', x);

/* Output:

inside async function
get x : 0   <-- async fn reads
main script
get x : 0
set x = 1
x : 1
set x = 5   <-- async fn writes
x : 5       <-- async fn logs

*/
/* Just to make console fill the available space */
.as-console-wrapper {
  max-height: 100% !important;
}


อาจจะน่าสังเกตว่า: "คุณอาจจะรู้ว่าx += yมันเหมือนกันx = (x + y)" - นี่ไม่ใช่กรณีในทุกสถานการณ์ในทุกภาษา แต่โดยทั่วไปคุณสามารถวางใจได้ว่าพวกเขาทำหน้าที่เหมือนกัน
นิค

11

คำสั่งของคุณx += await 5desugars ไป

const _temp = x;
await;
x = _temp + 5;

_tempค่า orary เป็น0และถ้าคุณเปลี่ยนxช่วงawait(ซึ่งรหัสของคุณไม่) มันไม่ได้เรื่องจะได้รับมอบหมาย5นั้น


9

รหัสนี้ค่อนข้างซับซ้อนที่จะติดตามเพราะใช้เวลา async ที่ไม่คาดคิดกระโดดข้ามไปมา ลองตรวจสอบ (ใกล้) ว่าจะดำเนินการอย่างไรและฉันจะอธิบายว่าทำไมหลังจากนั้น ฉันได้เปลี่ยนบันทึกของคอนโซลเพื่อเพิ่มตัวเลข - ทำให้การอ้างถึงพวกเขาง่ายขึ้นและยังแสดงให้เห็นถึงสิ่งที่ดีกว่า:

let x = 0;                        // 1 declaring and assigning x

async function test() {           // 2 function declaration
    x += await 5;                 // 4/7 assigning x
    console.log('x1 :', x);       // 8 printing
}

test();                           // 3 invoking the function
x += 1;                           // 5 assigning x
console.log('x2 :', x);           // 6 printing

ดังนั้นรหัสไม่ได้ตรงไปตรงมาแน่นอนว่าแน่นอน และเราก็มี4/7สิ่งที่แปลกเช่นกัน และนั่นคือปัญหาทั้งหมดที่นี่

ก่อนอื่นเรามาอธิบาย - ฟังก์ชั่น async ไม่ใช่แบบอะซิงโครนัสอย่างเคร่งครัด พวกเขาจะหยุดการทำงานชั่วคราวและทำงานต่อในภายหลังหากใช้awaitคำหลัก ก็ไม่มีพวกเขาดำเนินการจากบนลงล่างแสดงออกหลังจากการแสดงออกพร้อมกัน:

async function foo() {
  console.log("--one");
  console.log("--two");
}

console.log("start");
foo();
console.log("end");

async function foo() {
  console.log("--one");
  await 0; //just satisfy await with an expression
  console.log("--two");
}

console.log("start");
foo();
console.log("end");

ดังนั้นสิ่งแรกที่เราต้องรู้ว่าการใช้awaitจะทำให้ส่วนที่เหลือของฟังก์ชั่นดำเนินการในภายหลัง ในตัวอย่างที่ระบุนั่นหมายความว่าconsole.log('x1 :', x)จะถูกดำเนินการหลังจากส่วนที่เหลือของรหัสซิงโครนัส นั่นเป็นเพราะสัญญาใด ๆ จะได้รับการแก้ไขหลังจากเหตุการณ์วนรอบปัจจุบันเสร็จสิ้น

ดังนั้นนี้อธิบายได้ว่าทำไมเราได้รับx2 : 1ลงทะเบียนครั้งแรกและเหตุผลที่x2 : 5ถูกบันทึกไว้ที่สอง 5แต่ไม่ว่าทำไมค่าหลังเป็น เหตุผลx += await 5ควร5... แต่นี่คือการจับคู่กับawaitคำหลัก - มันจะหยุดการทำงานของฟังก์ชั่น แต่สิ่งใด ๆ ก่อนที่มันจะได้ทำงานแล้ว x += await 5กำลังจะถูกประมวลผลในลักษณะดังต่อไปนี้

  1. xดึงข้อมูลค่าของ 0ในช่วงเวลาของการดำเนินการที่ว่า
  2. await5การแสดงออกต่อไปซึ่งเป็น ดังนั้นฟังก์ชั่นหยุดตอนนี้และจะกลับมาทำงานต่อในภายหลัง
  3. ใช้งานฟังก์ชันต่อ นิพจน์ได้รับการแก้ไขเป็น 5
  4. เพิ่มค่าจาก 1 และนิพจน์จาก 2/3: 0 + 5
  5. กำหนดค่าตั้งแต่ 4. ถึง x

ดังนั้นฟังก์ชั่นจะหยุดหลังจากที่อ่านนั่นxคือ0และดำเนินการต่อเมื่อมีการเปลี่ยนแปลงไปแล้วอย่างไรก็ตามมันไม่ได้อ่านค่าของxอีกครั้ง

หากเราแกะสิ่งawaitที่Promiseเทียบเท่าที่จะดำเนินการคุณมี:

let x = 0;                        // 1 declaring and assigning x

async function test() {           // 2 function declaration
    const temp = x;               // 4 value read of x
    await 0; //fake await to pause for demo
    return new Promise((resolve) => {
      x = temp + 5;               // 7 assign to x
      console.log('x1 :', x);     // 8 printing
      resolve();
    });
}

test();                           // 3 invoking the function
x += 1;                           // 5 assigning x
console.log('x2 :', x);           // 6 printing


3

Ya มันค่อนข้างยุ่งยากสิ่งที่เกิดขึ้นจริงคือการดำเนินการเพิ่มเติมทั้งสองเกิดขึ้น parellaly ดังนั้นการดำเนินการจะเป็นเช่น:

ภายในสัญญา: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

ข้างนอก: x += 1==> x = x + 1==> x = 0 + 1==>1

เนื่องจากการดำเนินการทั้งหมดข้างต้นเกิดขึ้นจากซ้ายไปขวาส่วนแรกของการเพิ่มอาจถูกคำนวณในเวลาเดียวกันและเนื่องจากมีการรอก่อนหน้า 5 ที่ additio อาจล่าช้าเล็กน้อย คุณสามารถดูการดำเนินการได้โดยใส่จุดพักในโค้ด


1

Async และ Await เป็นส่วนขยายของสัญญา ฟังก์ชั่น async สามารถมีการแสดงออกที่รอที่จะหยุดการทำงานของฟังก์ชั่น async และรอการแก้ปัญหาของสัญญาผ่านแล้วดำเนินการต่อการทำงานของฟังก์ชั่น async และส่งกลับค่าการแก้ไข โปรดจำไว้ว่าคำหลักที่รอคอยใช้ได้เฉพาะในฟังก์ชั่น async เท่านั้น

แม้ว่าคุณจะเปลี่ยนค่าของ x หลังจากเรียกใช้ฟังก์ชันทดสอบแล้วก็ตามค่าของ x จะยังคงเป็น 0 เพราะฟังก์ชั่น async ได้สร้างขึ้นแล้วเป็นอินสแตนซ์ใหม่ ความหมายทุกอย่างเปลี่ยนแปลงตัวแปรภายนอกมันจะไม่เปลี่ยนค่าภายในของมันหลังจากที่มันถูกเรียก ถ้าคุณไม่เพิ่มส่วนที่สูงกว่าฟังก์ชั่นการทดสอบ


" ความหมายทุกอย่างเปลี่ยนแปลงตัวแปรภายนอกมันจะไม่เปลี่ยนค่าภายในของมันหลังจากถูกเรียกว่า ": นั่นไม่จริง ฟังก์ชั่น Async จะได้รับการเปลี่ยนแปลงตัวแปรในระหว่างการดำเนินการ ลองทำสิ่งนี้: let x="Didn't receive change"; (async()=>{await 'Nothing'; console.log(x); await new Promise(resolve=>setTimeout(resolve,2000)); console.log(x)})(); x='Received synchronous change'; setTimeout(()=>{x='Received change'},1000)มันส่งออกReceived synchronous changeและReceived change
FZs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.