Làm cách nào để đợi một Lời hứa JavaScript giải quyết trước khi tiếp tục chức năng?


107

Tôi đang thực hiện một số thử nghiệm đơn vị. Khung thử nghiệm tải một trang vào iFrame và sau đó chạy các xác nhận đối với trang đó. Trước khi mỗi thử nghiệm bắt đầu, tôi tạo một Promisethiết lập onloadsự kiện của iFrame để gọi resolve(), đặt iFrame srcvà trả về lời hứa.

Vì vậy, tôi chỉ có thể gọi loadUrl(url).then(myFunc)và nó sẽ đợi trang tải trước khi thực thi bất kỳ điều gìmyFunc .

Tôi sử dụng loại mẫu này khắp nơi trong các thử nghiệm của mình (không chỉ để tải URL), chủ yếu để cho phép các thay đổi đối với DOM xảy ra (ví dụ: bắt chước cách nhấp vào một nút và đợi các div ẩn và hiển thị).

Nhược điểm của thiết kế này là tôi liên tục viết các hàm ẩn danh với một vài dòng mã trong đó. Hơn nữa, trong khi tôi có một công việc xung quanh (QUnit'sassert.async() ), hàm kiểm tra xác định các lời hứa sẽ hoàn thành trước khi lời hứa được chạy.

Tôi tự hỏi liệu có cách nào để lấy giá trị từ a Promisehoặc đợi (khối / ngủ) cho đến khi nó được giải quyết, tương tự như .NET'sIAsyncResult.WaitHandle.WaitOne() . Tôi biết JavaScript là một luồng, nhưng tôi hy vọng điều đó không có nghĩa là một hàm không thể mang lại.

Về bản chất, có cách nào sau đây để nhổ cho kết quả theo đúng thứ tự?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


nếu bạn đặt lời gọi chắp thêm vào một hàm có thể sử dụng lại, bạn có thể () khi cần thiết để KHÔ. bạn cũng có thể tạo các trình xử lý đa năng, được điều khiển bởi điều này để cung cấp các lệnh gọi then (), chẳng hạn như .then(fnAppend.bind(myDiv)), điều này có thể cắt giảm đáng kể các anons.
dandavis

Bạn đang thử nghiệm với cái gì? Nếu đó là một trình duyệt hiện đại hoặc bạn có thể sử dụng một công cụ như BabelJS để chuyển mã của bạn thì điều này chắc chắn là có thể.
Benjamin Gruenbaum

Câu trả lời:


78

Tôi tự hỏi liệu có cách nào để lấy giá trị từ Promise hay đợi (khối / ngủ) cho đến khi nó được giải quyết, tương tự như IAsyncResult.WaitHandle.WaitOne () của .NET. Tôi biết JavaScript là một luồng, nhưng tôi hy vọng điều đó không có nghĩa là một hàm không thể mang lại.

Thế hệ hiện tại của Javascript trong các trình duyệt không có wait()hoặc sleep()cho phép những thứ khác chạy. Vì vậy, bạn chỉ đơn giản là không thể làm những gì bạn đang yêu cầu. Thay vào đó, nó có các hoạt động không đồng bộ sẽ thực hiện công việc của họ và sau đó gọi cho bạn khi họ hoàn thành (như bạn đã sử dụng các lời hứa).

Một phần của điều này là do tính đơn luồng của Javascript. Nếu một chuỗi đang quay, thì không Javascript nào khác có thể thực thi cho đến khi chuỗi quay đó được hoàn thành. ES6 giới thiệu yieldvà trình tạo sẽ cho phép một số thủ thuật hợp tác như vậy, nhưng chúng tôi có khá nhiều cách để có thể sử dụng chúng trong một loạt các trình duyệt đã cài đặt (chúng có thể được sử dụng trong một số phát triển phía máy chủ nơi bạn kiểm soát công cụ JS đang được sử dụng).


Quản lý cẩn thận mã dựa trên lời hứa có thể kiểm soát thứ tự thực hiện cho nhiều hoạt động không đồng bộ.

Tôi không chắc mình hiểu chính xác thứ tự bạn đang cố gắng đạt được trong mã của mình, nhưng bạn có thể làm điều gì đó như thế này bằng cách sử dụng kickOff()hàm hiện có của mình và sau đó gắn một .then()trình xử lý vào nó sau khi gọi nó:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

Điều này sẽ trả lại đầu ra theo thứ tự đảm bảo - như thế này:

start
middle
end

Cập nhật vào năm 2018 (ba năm sau khi câu trả lời này được viết):

Nếu bạn chuyển mã hoặc chạy mã của mình trong môi trường hỗ trợ các tính năng của ES7 chẳng hạn như asyncawait, bây giờ bạn có thể sử dụng awaitđể làm cho mã của bạn "xuất hiện" để chờ kết quả của một lời hứa. Nó vẫn đang phát triển với những hứa hẹn. Nó vẫn không chặn tất cả Javascript, nhưng nó cho phép bạn viết các hoạt động tuần tự theo một cú pháp thân thiện hơn.

Thay vì cách làm việc của ES6:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

Bạn có thể làm được việc này:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

3
Đúng, sử dụng then()là những gì tôi đã và đang làm. Tôi chỉ không thích phải viết function() { ... }mọi lúc. Nó làm lộn xộn mã.
dx_over_dt

3
@dfoverdx - mã hóa không đồng bộ trong Javascript luôn liên quan đến các lệnh gọi lại, vì vậy bạn luôn phải xác định một hàm (ẩn danh hoặc được đặt tên). Không có cách nào xung quanh nó hiện tại.
jfriend00

2
Mặc dù bạn có thể sử dụng ký hiệu mũi tên ES6 để làm cho nó ngắn gọn hơn. (foo) => { ... }thay vìfunction(foo) { ... }
Rob H

1
Thêm vào bình luận @RobH khá thường xuyên, bạn cũng có thể viết foo => single.expression(here)để loại bỏ dấu ngoặc nhọn và tròn.
begie,

3
@dfoverdx - Ba năm sau câu trả lời của tôi, tôi đã cập nhật nó với ES7 asyncawaitcú pháp như một tùy chọn hiện có sẵn trong node.js và trong các trình duyệt hiện đại hoặc thông qua transpilers.
jfriend00

15

Nếu sử dụng ES2016 bạn có thể sử dụng asyncawaitvà làm điều gì đó như:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

Nếu sử dụng ES2015, bạn có thể sử dụng Máy phát điện . Nếu bạn không thích cú pháp, bạn có thể trừu tượng hóa nó bằng một asynchàm tiện ích như được giải thích ở đây .

Nếu sử dụng ES5, bạn có thể muốn có một thư viện như Bluebird để cung cấp cho bạn nhiều quyền kiểm soát hơn.

Cuối cùng, nếu thời gian chạy của bạn hỗ trợ ES2015 thì thứ tự thực thi có thể được giữ nguyên song song bằng cách sử dụng Fetch Injection .


1
Ví dụ về trình tạo của bạn không hoạt động, nó đang thiếu một người chạy. Vui lòng không đề xuất máy phát điện nữa khi bạn chỉ có thể chuyển đổi ES8 async/ await.
Bergi

3
Cảm ơn phản hồi của bạn. Thay vào đó, tôi đã xóa ví dụ về Trình tạo và liên kết với hướng dẫn về Trình tạo toàn diện hơn với thư viện OSS liên quan.
Josh Habdas

9
Điều này chỉ đơn giản là kết thúc lời hứa. Hàm async sẽ không chờ đợi bất cứ điều gì khi bạn chạy nó, nó sẽ chỉ trả về một lời hứa. async/ awaitchỉ là một cú pháp khác cho Promise.then.
gỉyx

Bạn hoàn toàn đúng asyncsẽ không chờ đợi ... trừ khi nó cho phép sử dụng await. Ví dụ ES2016 / ES7 vẫn không thể chạy SO vì vậy đây là một số mã tôi đã viết ba năm trước để chứng minh hành vi.
Josh Habdas

7

Một tùy chọn khác là sử dụng Promise.all để đợi một loạt các lời hứa giải quyết. Đoạn mã dưới đây cho thấy cách chờ tất cả các lời hứa giải quyết và sau đó xử lý kết quả khi tất cả chúng đã sẵn sàng (vì đó dường như là mục tiêu của câu hỏi); Tuy nhiên, start rõ ràng có thể xuất ra trước khi middle được giải quyết chỉ bằng cách thêm mã đó trước khi nó gọi giải quyết.

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


1

Bạn có thể làm điều đó bằng tay. (Tôi biết, đó không phải là giải pháp tuyệt vời, nhưng ..) sử dụng whilevòng lặp cho đến khi resultkhông có giá trị

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

điều này sẽ tiêu tốn toàn bộ cpu trong khi chờ đợi.
Lukasz Guminski

đây là một giải pháp khủng khiếp
JSON
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.