Hàm không đồng bộ với + =


63

let x = 0;

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

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

Các giá trị của nhật xký là 15. Câu hỏi của tôi là: tại sao giá trị của x 5bản ghi thứ hai?

Nếu testđược thực hiện sau x += 1(vì nó là hàm async) thì giá trị của x là 1 theo thời gian testđược thực thi, do đó x += await 5sẽ tạo giá trị của x 6.


1
Bạn phải biết sự khác biệt giữa await (x += 5)x += await 5.
Singhi John

Câu trả lời:


60

TL; DR: Bởi vì +=đọc xtrước, nhưng viết nó sau khi nó đã thay đổi, do awaittừ khóa trong toán hạng thứ hai của nó (phía bên tay phải).


asynccác hàm chạy đồng bộ khi chúng được gọi cho đến awaitcâu lệnh đầu tiên .

Vì vậy, nếu bạn loại bỏ await, nó hoạt động như một hàm bình thường (với ngoại lệ là nó vẫn trả về một Promise).

Trong trường hợp đó, bạn nhận 56trong bảng điều khiển:

let x = 0;

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

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

Cái đầu tiên awaitdừng chạy đồng bộ, ngay cả khi đối số của nó có sẵn đồng bộ, do đó, phần sau sẽ trả về 16, như bạn mong đợi:

let x = 0;

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

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

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

Tuy nhiên, trường hợp của bạn phức tạp hơn một chút.

Bạn đã đặt awaittrong một biểu thức, sử dụng +=.

Bạn có thể biết, rằng trong JS x += ylà giống hệt nhau x = (x + y). Tôi sẽ sử dụng mẫu sau để hiểu rõ hơn:

let x = 0;

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

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

Khi thông dịch viên đạt đến dòng này ...

x = (x + await 5);

... nó bắt đầu đánh giá nó, và nó chuyển sang ...

x = (0 + await 5);

... Sau đó, nó đạt đến awaitvà dừng lại.

Mã sau khi gọi hàm bắt đầu chạy và sửa đổi giá trị của x, sau đó ghi nhật ký.

xtại là 1.

Sau đó, sau khi tập lệnh chính thoát, trình thông dịch quay trở lại testchức năng bị tạm dừng và tiếp tục đánh giá dòng đó:

x = (0 + 5);

Và, vì giá trị của xđã được thay thế, nó vẫn còn 0.

Cuối cùng, người phiên dịch làm việc bổ sung, các cửa hàng 5để x, và ghi lại nó.

Bạn có thể kiểm tra hành vi này bằng cách đăng nhập bên trong một getter / setter thuộc tính đối tượng (trong ví dụ này, y.zphản ánh giá trị của 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;
}


Có lẽ đáng chú ý: "Bạn có thể biết, đó x += ylà giống hệt với x = (x + y)." - Đây không phải là trường hợp trong mọi tình huống trong mọi ngôn ngữ, nhưng nói chung bạn có thể tin tưởng vào chúng hành động như nhau.
Nick

11

Tuyên bố của bạn x += await 5desugars để

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

Các _tempgiá trị orary là 0, và nếu bạn thay đổi xtrong suốt await(mà mã của bạn không) nó không quan trọng, nó được phân công 5sau đó.


9

Mã này khá phức tạp để theo dõi vì phải mất một số async bất ngờ nhảy qua lại. Hãy xem xét (gần) cách nó thực sự sẽ được thực thi và tôi sẽ giải thích lý do tại sao sau đó. Tôi cũng đã thay đổi nhật ký giao diện điều khiển để thêm số - giúp việc tham khảo chúng dễ dàng hơn và cũng hiển thị tốt hơn những gì được ghi:

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

Vì vậy, mã không thực sự đi theo một cách thẳng thắn, đó là điều chắc chắn. Và chúng tôi có một 4/7điều kỳ lạ là tốt. Và đó thực sự là toàn bộ vấn đề ở đây.

Trước hết, hãy làm rõ - các hàm async không thực sự không đồng bộ. Họ sẽ chỉ tạm dừng việc thực hiện và tiếp tục lại sau nếu awaittừ khóa được sử dụng. Không có nó, chúng thực thi từ trên xuống dưới, biểu thức sau biểu thức đồng bộ:

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");

Vì vậy, điều đầu tiên chúng ta cần biết rằng việc sử dụng awaitsẽ làm cho phần còn lại của hàm thực thi sau này. Trong ví dụ đã cho, điều đó có nghĩa console.log('x1 :', x)là sẽ được thực thi sau phần còn lại của mã đồng bộ. Đó là bởi vì bất kỳ Lời hứa nào sẽ được giải quyết sau khi vòng lặp sự kiện hiện tại kết thúc.

Vì vậy, điều này giải thích tại sao chúng ta được x2 : 1đăng nhập đầu tiên và tại sao x2 : 5được ghi lại thứ hai nhưng không phải tại sao giá trị sau lại 5. Về mặt logic x += await 5nên là 5... nhưng đây là lần bắt thứ hai cho awaittừ khóa - nó sẽ tạm dừng việc thực thi chức năng nhưng bất cứ điều gì trước khi nó đã chạy. x += await 5thực sự sẽ được xử lý theo cách sau

  1. Lấy giá trị của x. Tại thời điểm thực hiện, đó là 0.
  2. awaitbiểu thức tiếp theo là 5. Vì vậy, chức năng tạm dừng bây giờ và sẽ được nối lại sau.
  3. Tiếp tục chức năng. Biểu thức được giải quyết là 5.
  4. Thêm giá trị từ 1. và biểu thức từ 2/3: 0 + 5
  5. Gán giá trị từ 4. đến x

Vì vậy, tạm dừng chức năng sau khi nó đọc mà x0và tiếp tục khi nó đã được thay đổi, tuy nhiên, nó không đọc lại giá trị củax .

Nếu chúng tôi mở awaitkhóa Promisetương đương sẽ thực thi, bạn có:

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 chút rắc rối của nó những gì thực sự xảy ra là cả hai hoạt động bổ sung đang diễn ra ngang nhau nên hoạt động sẽ như sau:

Trong lời hứa: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Bên ngoài: x += 1==> x = x + 1==> x = 0 + 1==>1

vì tất cả các hoạt động trên đang diễn ra từ trái sang phải, phần đầu tiên của phép cộng có thể được tính toán cùng một lúc và vì có sự chờ đợi trước 5 nên additio có thể trì hoãn một chút. Bạn có thể thấy việc thực hiện bằng cách đặt điểm dừng trong mã.


1

Async và Await là phần mở rộng của lời hứa. Hàm async có thể chứa biểu thức chờ đợi tạm dừng thực thi chức năng async và chờ độ phân giải của Promise đã thông qua, sau đó tiếp tục thực thi chức năng async và trả về giá trị đã giải quyết. Hãy nhớ rằng, từ khóa await chỉ có giá trị bên trong các chức năng async.

Ngay cả khi bạn đã thay đổi giá trị của x sau khi gọi hàm kiểm tra, giá trị của x sẽ vẫn là 0 vì hàm async đã tạo ra phiên bản mới. Có nghĩa là mọi thứ thay đổi trên biến bên ngoài của nó sẽ không thay đổi giá trị bên trong của nó sau khi nó được gọi. Trừ khi bạn đặt số gia của bạn lên trên hàm kiểm tra.


" Có nghĩa là mọi thứ thay đổi trên biến bên ngoài nó sẽ không thay đổi giá trị bên trong của nó sau khi nó được gọi ": điều đó không đúng. Các hàm Async không nhận được các thay đổi trong quá trình thực thi. Chỉ cần thử điều này: 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)Nó xuất ra Received synchronous changeReceived change
FZ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.