Cách thích hợp để chờ một chức năng kết thúc trước khi tiếp tục?


187

Tôi có hai hàm JS. Người này gọi người kia. Trong chức năng gọi điện, tôi muốn gọi cho người khác, đợi chức năng đó kết thúc, sau đó tiếp tục. Vì vậy, ví dụ / mã giả:

function firstFunction(){
    for(i=0;i<x;i++){
        // do something
    }
};

function secondFunction(){
    firstFunction()
    // now wait for firstFunction to finish...
    // do something else
};

Tôi đã đưa ra giải pháp này, nhưng không biết liệu đây có phải là một cách thông minh để thực hiện nó không.

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()
    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

Điều đó có hợp pháp không? Có cách nào thanh lịch hơn để xử lý nó? Có lẽ với jQuery?


11
Có gì không firstFunctionchính xác làm điều đó làm cho nó không đồng bộ? Dù bằng cách nào - hãy kiểm tra về những lời hứa
zerkms

Chức năng đầu tiên là nhanh chóng cập nhật đồng hồ điểm số cứ sau 10 giây. Mà ... nghĩ về nó, tôi cho rằng chúng ta có thể tính toán trước và sau đó chỉ cần tạm dừng cuộc gọi chức năng thứ hai thông qua setTimeout. Điều đó nói rằng, tôi vẫn có thể thấy một mong muốn có một khả năng tạm dừng ở nơi khác.
DA.

bạn không cần tạm dừng - google cho những lời hứa trong jquery
zerkms

@zerkms hứa hẹn có vẻ thú vị! Vẫn đang điều tra hỗ trợ trình duyệt ...
DA.

1
Tôi cũng có cùng một vấn đề.
Vappor Washcraft

Câu trả lời:


141

Một cách để xử lý công việc không đồng bộ như thế này là sử dụng chức năng gọi lại, ví dụ:

function firstFunction(_callback){
    // do some asynchronous work
    // and when the asynchronous stuff is complete
    _callback();    
}

function secondFunction(){
    // call first function and pass in a callback function which
    // first function runs when it has completed
    firstFunction(function() {
        console.log('huzzah, I\'m done!');
    });    
}

Theo đề xuất của @Janaka Pushpakumara, giờ đây bạn có thể sử dụng các hàm mũi tên để đạt được điều tương tự. Ví dụ:

firstFunction(() => console.log('huzzah, I\'m done!'))


Cập nhật: Tôi đã trả lời điều này khá lâu rồi và thực sự muốn cập nhật nó. Mặc dù các cuộc gọi lại hoàn toàn tốt, nhưng theo kinh nghiệm của tôi, chúng có xu hướng dẫn đến mã khó đọc và bảo trì hơn. Có những tình huống tôi vẫn sử dụng chúng, chẳng hạn như để vượt qua trong các sự kiện tiến trình và tương tự như các tham số. Bản cập nhật này chỉ để nhấn mạnh các lựa chọn thay thế.

Ngoài ra, câu hỏi ban đầu không đề cập cụ thể đến async, vì vậy trong trường hợp bất kỳ ai cũng nhầm lẫn, nếu chức năng của bạn là đồng bộ, nó sẽ chặn khi được gọi. Ví dụ:

doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()

Nếu mặc dù hàm ý là chức năng không đồng bộ, cách tôi có xu hướng xử lý tất cả các công việc không đồng bộ của tôi ngày hôm nay là với async / await. Ví dụ:

const secondFunction = async () => {
  const result = await firstFunction()
  // do something else here after firstFunction completes
}

IMO, async / await làm cho mã của bạn dễ đọc hơn nhiều so với sử dụng lời hứa trực tiếp (hầu hết thời gian). Nếu bạn cần xử lý lỗi bắt thì sử dụng nó với thử / bắt. Đọc thêm về nó ở đây: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_feft .


9
@zerkms - Chăm sóc công phu?
Matt Way

7
cuộc gọi lại bất tiện để xử lý sự không đồng bộ, các lời hứa sẽ dễ dàng và linh hoạt hơn nhiều
zerkms

1
Mặc dù bạn đúng rằng tôi không nên sử dụng từ tốt nhất (cập nhật), sự tiện lợi của các cuộc gọi lại so với lời hứa phụ thuộc vào mức độ phức tạp của vấn đề.
Matt Way

3
Nó trở nên không chính đáng, nhưng cá nhân tôi không thấy lý do nào thích gọi lại trong những ngày này :-) Không thể nhớ bất kỳ mã nào của tôi được viết trong 2 năm qua cung cấp các cuộc gọi lại về lời hứa.
zerkms

3
Nếu bạn muốn, bạn có thể sử dụng chức năng mũi tên trong es6 secondFunction () {firstFunction ((hồi đáp) => {console.log (hồi đáp);}); }
Janaka Pushpakumara

53

Sử dụng async / await:

async function firstFunction(){
  for(i=0;i<x;i++){
    // do something
  }
  return;
};

sau đó sử dụng await trong chức năng khác của bạn để chờ nó trở lại:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};

9
đối với những người trong chúng ta bị mắc kẹt trong việc hỗ trợ các trình duyệt cũ hơn, IE không hỗ trợ async / await
kyle

Điều này có thể được sử dụng bên ngoài cả hai chức năng, như trong , $(function() { await firstFunction(); secondFunction(); });?
Lewistrick

1
@Lewistrick Chỉ cần nhớ awaitchỉ có thể được sử dụng trong một asyncphương thức. Vì vậy, nếu bạn thực hiện chức năng cha mẹ, asyncbạn có thể gọi bao nhiêu async methodsbên trong có hoặc không có awaitmà bạn muốn.
Fawaz

Có thể awaita setTimeout?
Shaya

1
@Shaya đúng vậy, hãy bọc setTimeout trong một chức năng khác để giải quyết một lời hứa sau khi hết thời gian.
Fawaz

51

Có vẻ như bạn đang thiếu một điểm quan trọng ở đây: JavaScript là một môi trường thực thi đơn luồng. Hãy xem lại mã của bạn, lưu ý tôi đã thêm alert("Here"):

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()

    alert("Here");

    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

Bạn không cần phải chờ đợi isPaused. Khi bạn thấy cảnh báo "Tại đây", isPausedsẽ có falsefirstFunctionsẽ quay lại. Đó là bởi vì bạn không thể "nhường" từ bên trong forvòng lặp ( // do something), vòng lặp có thể không bị gián đoạn và sẽ phải hoàn thành đầy đủ trước tiên (chi tiết hơn: xử lý luồng Javascript và điều kiện cuộc đua ).

Điều đó nói rằng, bạn vẫn có thể làm cho dòng mã bên trong firstFunctionkhông đồng bộ và sử dụng hoặc gọi lại hoặc hứa để thông báo cho người gọi. Thay vào đó, bạn phải từ bỏ forvòng lặp và mô phỏng nó bằng if( JSFiddle ):

function firstFunction()
{
    var deferred = $.Deferred();

    var i = 0;
    var nextStep = function() {
        if (i<10) {
            // Do something
            printOutput("Step: " + i);
            i++;
            setTimeout(nextStep, 500); 
        }
        else {
            deferred.resolve(i);
        }
    }
    nextStep();
    return deferred.promise();
}

function secondFunction()
{
    var promise = firstFunction();
    promise.then(function(result) { 
        printOutput("Result: " + result);
    });
}

Bên cạnh đó, JavaScript 1.7 đã giới thiệu yieldtừ khóa như một phần của trình tạo . Điều đó sẽ cho phép "đục lỗ" các lỗ không đồng bộ trong luồng mã JavaScript đồng bộ khác ( chi tiết hơn và một ví dụ ). Tuy nhiên, hỗ trợ trình duyệt cho máy phát điện hiện bị giới hạn ở Firefox và Chrome, AFAIK.


3
Bạn đã cứu tôi. $ .Deferred () là những gì tôi đang sử dụng. Cảm ơn
Temitayo

@noseratio Mình đã thử cái này. Nhưng phương thức WaitForIt hoàn toàn không được gọi. Tôi đang làm gì sai?
cảnh giác

@vigamage, bạn có thể cung cấp một liên kết đến jsfiddle hoặc codepen về những gì bạn đã thử?
19:51

1
Xin chào, Cảm ơn bạn đã quan tâm. Tôi đã làm cho nó hoạt động. Cảm ơn bạn..!
cảnh giác

18

Một cách thanh lịch để chờ đợi một chức năng hoàn thành trước tiên là sử dụng Promise với chức năng async / await .


  1. Đầu tiên, tạo một lời hứa . Hàm tôi tạo sẽ được hoàn thành sau 2 giây. Tôi đã sử dụng setTimeoutđể chứng minh tình huống mà các hướng dẫn sẽ mất một thời gian để thực thi.
  2. Đối với chức năng thứ hai, bạn có thể sử dụng chức năng async / await nơi bạn sẽ awaitcho chức năng đầu tiên hoàn thành trước khi tiếp tục với các hướng dẫn.

Thí dụ:

    //1. Create a new function that returns a promise
    function firstFunction() {
      return new Promise((resolve, reject) => {
          let y = 0
          setTimeout(() => {
            for(i=0; i<10; i++){
               y++
            }
             console.log('loop completed')  
             resolve(y)
          }, 2000)
      })
    }
    
    //2. Create an async function
    async function secondFunction() {
        console.log('before promise call')
        //3. Await for the first function to complete
        let result = await firstFunction()
        console.log('promise resolved: ' + result)
        console.log('next step')
    }; 

    secondFunction()


Ghi chú:

Bạn có thể chỉ đơn giản resolvePromisekhông có bất kỳ giá trị như vậy resolve(). Trong ví dụ của tôi, tôi resolvedPromisevới giá trị ymà tôi sau đó có thể sử dụng trong hàm thứ hai.


vâng điều này sẽ luôn luôn làm việc. nếu có ai tham gia vào loại kịch bản này thì JavaScript Promise sẽ thực hiện thủ thuật cho họ.
Puttamarigowda MS

5

Vấn đề duy nhất với lời hứa là IE không hỗ trợ họ. Edge có, nhưng có rất nhiều IE 10 và 11 ngoài kia: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (khả năng tương thích ở phía dưới)

Vì vậy, JavaScript là một luồng đơn. Nếu bạn không thực hiện cuộc gọi không đồng bộ, nó sẽ hoạt động theo dự đoán. Chuỗi JavaScript chính sẽ thực thi hoàn toàn một chức năng trước khi thực hiện chức năng tiếp theo, theo thứ tự chúng xuất hiện trong mã. Thứ tự đảm bảo cho các chức năng đồng bộ là không đáng kể - mỗi chức năng sẽ thực hiện hoàn toàn theo thứ tự mà nó được gọi.

Hãy nghĩ về chức năng đồng bộ như một đơn vị nguyên tử của công việc . Chuỗi JavaScript chính sẽ thực thi nó đầy đủ, theo thứ tự các câu lệnh xuất hiện trong mã.

Nhưng, ném vào cuộc gọi không đồng bộ, như trong tình huống sau:

showLoadingDiv(); // function 1

makeAjaxCall(); // function 2 - contains async ajax call

hideLoadingDiv(); // function 3

Điều này không làm những gì bạn muốn . Nó ngay lập tức thực thi chức năng 1, chức năng 2 và chức năng 3. Đang tải div flash và nó đã biến mất, trong khi cuộc gọi ajax gần như không hoàn thành, mặc dù makeAjaxCall()đã trả về. KHIẾU NẠImakeAjaxCall()đã chia công việc của nó thành các khối được nâng cao từng chút một theo từng vòng của luồng JavaScript chính - nó hoạt động không đồng bộ. Nhưng cùng một luồng chính, trong một lần quay / chạy, đã thực hiện các phần đồng bộ một cách nhanh chóng và có thể dự đoán được.

Vì vậy, cách tôi xử lý nó : Giống như tôi đã nói chức năng là đơn vị nguyên tử của công việc. Tôi đã kết hợp mã của hàm 1 và 2 - Tôi đặt mã của hàm 1 vào hàm 2, trước khi gọi asynch. Tôi đã loại bỏ chức năng 1. Mọi thứ lên đến và bao gồm cả cuộc gọi không đồng bộ thực hiện theo thứ tự dự đoán.

THEN, khi cuộc gọi không đồng bộ hoàn thành, sau một vài lần quay của luồng JavaScript chính, nó có chức năng gọi 3. Điều này đảm bảo thứ tự . Ví dụ, với ajax, trình xử lý sự kiện onreadystatechange được gọi nhiều lần. Khi nó báo cáo nó đã hoàn thành, sau đó gọi hàm cuối cùng bạn muốn.

Tôi đồng ý nó lộn xộn hơn. Tôi thích có mã đối xứng, tôi thích có các hàm làm một việc (hoặc gần với nó) và tôi không thích có cuộc gọi ajax theo bất kỳ cách nào chịu trách nhiệm cho màn hình (tạo ra sự phụ thuộc vào người gọi). NHƯNG, với một cuộc gọi không đồng bộ được nhúng trong một chức năng đồng bộ, các thỏa hiệp phải được thực hiện để đảm bảo thứ tự thực hiện. Và tôi phải viết mã cho IE 10 nên không hứa hẹn.

Tóm tắt : Đối với các cuộc gọi đồng bộ, thứ tự đảm bảo là không đáng kể. Mỗi hàm thực hiện đầy đủ theo thứ tự nó được gọi. Đối với một chức năng có một cuộc gọi không đồng bộ, cách duy nhất để đảm bảo trật tự là theo dõi khi cuộc gọi async hoàn thành và gọi chức năng thứ ba khi phát hiện trạng thái đó.

Để thảo luận về các chủ đề JavaScript, hãy xem: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23https://developer.mozilla.org/en-US/docs/Web/JavaScript/ Sự kiện

Ngoài ra, một câu hỏi tương tự, được đánh giá cao về chủ đề này: Làm thế nào tôi nên gọi 3 hàm để thực hiện lần lượt từng hàm?


8
Đối với người dùng downvoter, tôi tò mò muốn biết điều gì sai với câu trả lời này.
kermit

0

Đây là những gì tôi đã đưa ra, vì tôi cần phải chạy một số hoạt động trong một chuỗi.

<button onclick="tprom('Hello Niclas')">test promise</button>

<script>
    function tprom(mess) {
        console.clear();

        var promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(mess);
            }, 2000);
        });

        var promise2 = new Promise(async function (resolve, reject) {
            await promise;
            setTimeout(function () {
                resolve(mess + ' ' + mess);
            }, 2000);
        });

        var promise3 = new Promise(async function (resolve, reject) {
            await promise2;
            setTimeout(function () {
                resolve(mess + ' ' + mess+ ' ' + mess);
            }, 2000);
        });

        promise.then(function (data) {
            console.log(data);
        });

        promise2.then(function (data) {
            console.log(data);
        });

        promise3.then(function (data) {
            console.log(data);
        });
    }

</script>

Vui lòng thêm ít nhất một bình luận cấp cao để mô tả đoạn trích đã cho. Điều này là để giải thích làm thế nào mã của bạn giải quyết vấn đề.
Cerberus lạnh

Vấn đề là chạy chức năng thứ hai sau chức năng thứ nhất. Tôi chỉ ra cách chạy hàm thứ hai sau hàm thứ nhất và cả cách chạy hàm thứ ba sau hàm thứ hai. Tôi đã thực hiện ba chức năng, để dễ hiểu mã nào là bắt buộc.
Niclas Arnberger

0

Niềm vui chính của bạn sẽ gọi FirstFun sau đó khi hoàn thành nó, niềm vui tiếp theo của bạn sẽ gọi.

async firstFunction() {
            const promise = new Promise((resolve, reject) => {
                for (let i = 0; i < 5; i++) {
                    // do something
                    console.log(i);
                    if (i == 4) {
                        resolve(i);
                    }
                }
            });
            const result = await promise;
        }

        second() {
            this.firstFunction().then( res => {
                // third function call do something
                console.log('Gajender here');
            });
        }
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.