วิธีที่เหมาะสมในการรอให้หนึ่งฟังก์ชันเสร็จสิ้นก่อนดำเนินการต่อหรือไม่


187

ฉันมีฟังก์ชั่น JS สองตัว สายหนึ่งเรียกอีกสายหนึ่ง ภายในฟังก์ชั่นการโทรฉันต้องการโทรหาคนอื่นรอฟังก์ชั่นนั้นให้เสร็จจากนั้นจึงดำเนินการต่อ ดังนั้นสำหรับตัวอย่าง / รหัสหลอก:

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

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

ฉันคิดวิธีแก้ปัญหานี้ แต่ไม่รู้ว่านี่เป็นวิธีที่ชาญฉลาดหรือไม่

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
        };
    }
};

ถูกต้องหรือไม่ มีวิธีที่สง่างามกว่าที่จะจัดการกับมันหรือไม่? บางทีด้วย jQuery


11
อะไรfirstFunctionที่ทำให้มันไม่ตรงกัน? ไม่ว่าจะด้วยวิธีใด - ตรวจสอบเกี่ยวกับสัญญา
zerkms

ฟังก์ชั่นแรกคือการอัพเดทนาฬิกาคะแนนอย่างรวดเร็วทุก ๆ 10 วินาที ซึ่ง ... ลองคิดดูฉันคิดว่าเราสามารถคำนวณล่วงหน้าจากนั้นเพียงหยุดการเรียกใช้ฟังก์ชันที่สองผ่าน setTimeout ด้วยตนเอง ที่กล่าวว่าฉันยังคงเห็นความปรารถนาที่จะมีความสามารถในการหยุดชั่วคราวที่อื่น
ดา

คุณไม่จำเป็นต้องหยุดชั่วคราว - google สำหรับคำสัญญาใน jquery
zerkms

@zerkms สัญญาว่าจะดูน่าสนใจ! กำลังตรวจสอบการสนับสนุนเบราว์เซอร์ ...
DA

1
ฉันยังมีปัญหาเดียวกัน
Vappor Washmade

คำตอบ:


142

วิธีหนึ่งในการจัดการกับงานแบบอะซิงโครนัสเช่นนี้คือการใช้ฟังก์ชันการโทรกลับเช่น:

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!');
    });    
}

ตามคำแนะนำของ @Janaka Pushpakumara ตอนนี้คุณสามารถใช้ฟังก์ชั่นลูกศรเพื่อให้ได้สิ่งเดียวกัน ตัวอย่างเช่น:

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


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

นอกจากนี้คำถามเดิมไม่ได้เจาะจงถึง async ดังนั้นในกรณีที่ใครสับสนถ้าฟังก์ชั่นของคุณซิงโครนัสมันจะถูกบล็อกเมื่อถูกเรียก ตัวอย่างเช่น:

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

ถ้าฟังก์ชั่นนั้นเป็นแบบอะซิงโครนัสถ้าเป็นนัยทางที่ฉันมักจะจัดการกับงานอะซิงโครนัสทั้งหมดของฉันในวันนี้ก็คือ async / คอย ตัวอย่างเช่น:

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

IMO, async / await ทำให้โค้ดของคุณอ่านได้ง่ายกว่าการใช้สัญญาโดยตรง (เกือบตลอดเวลา) หากคุณจำเป็นต้องจัดการกับข้อผิดพลาดในการจับแล้วใช้กับลอง / จับ อ่านข้อมูลเกี่ยวกับมันมากขึ้นที่นี่: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function


9
@zerkms - สนใจที่จะทำอย่างละเอียด?
Matt Way

7
การโทรกลับไม่สะดวกในการจัดการกับแบบอะซิงโครนัสสัญญานั้นง่ายและยืดหยุ่นมากขึ้น
zerkms

1
ในขณะที่คุณพูดถูกฉันไม่ควรใช้คำที่ดีที่สุด (อัปเดต) ความสะดวกในการโทรกลับและสัญญาขึ้นอยู่กับความซับซ้อนของปัญหา
Matt Way

3
มันกลายเป็นความผิดพลาด แต่โดยส่วนตัวฉันไม่เห็นเหตุผลที่จะชอบการโทรกลับในวันนี้ :-) ไม่สามารถจำรหัสใด ๆ ของฉันที่เขียนในช่วง 2 ปีที่ผ่านมาได้
zerkms

3
หากคุณต้องการคุณสามารถใช้ฟังก์ชั่นลูกศรใน es6 secondFunction () {firstFunction ((การตอบสนอง) => {console.log (การตอบสนอง);}); }
Janaka Pushpakumara

53

ใช้ async / คอย:

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

จากนั้นใช้ฟังก์ชันรอของคุณในการรอให้มันกลับมา:

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

9
สำหรับพวกเราติดการสนับสนุนเบราว์เซอร์รุ่นเก่า IE ไม่สนับสนุน async / รอคอย
ไคล์

สามารถใช้นอกฟังก์ชั่นทั้งสองนี้ได้$(function() { await firstFunction(); secondFunction(); });หรือไม่
Lewistrick

1
@Lewistrick เพียงจำไว้ว่าawaitสามารถใช้ได้ภายในasyncเมธอดเท่านั้น ดังนั้นถ้าคุณสร้างฟังก์ชั่นหลักasyncคุณสามารถโทรasync methodsเข้าไปข้างในได้ไม่awaitว่าคุณต้องการ
Fawaz

มันเป็นไปได้ที่จะ? awaitsetTimeout
Shayan

1
@ Shayan ใช่มันคือห่อ setTimeout ในฟังก์ชั่นอื่นซึ่งแก้ไขสัญญาหลังจากหมดเวลา
Fawaz

51

ดูเหมือนว่าคุณไม่มีจุดสำคัญที่นี่: JavaScript เป็นสภาพแวดล้อมการประมวลผลแบบเธรดเดียว ลองดูรหัสของคุณอีกครั้งทราบว่าฉันได้เพิ่ม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
        };
    }
};

isPausedคุณไม่จำเป็นต้องรอให้ เมื่อคุณเห็นการแจ้งเตือน "ที่นี่" isPausedจะมีfalseอยู่แล้วและfirstFunctionจะกลับมา นั่นเป็นเพราะคุณไม่สามารถ "ให้ผลผลิต" จากภายในforลูป ( // do something) ลูปอาจไม่ถูกขัดจังหวะและจะต้องดำเนินการให้เสร็จสมบูรณ์ก่อน (รายละเอียดเพิ่มเติม: การจัดการเธรด Javascript และเงื่อนไขการแข่งขัน )

ที่กล่าวว่าคุณยังสามารถทำให้การไหลของรหัสภายในfirstFunctionเป็นแบบอะซิงโครนัสและใช้การโทรกลับหรือสัญญาเพื่อแจ้งให้ผู้โทรทราบ คุณจะต้องยอมแพ้เมื่อforลูปและจำลองด้วย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);
    });
}

สังเกตด้านบน, JavaScript 1.7 ได้แนะนำyieldคำหลักที่เป็นส่วนหนึ่งของเครื่องกำเนิดไฟฟ้า ที่จะอนุญาตให้ "punch" รูอะซิงโครนัสในโฟลว์โค้ด JavaScript แบบซิงโครนัส ( รายละเอียดเพิ่มเติมและตัวอย่าง ) อย่างไรก็ตามการสนับสนุนเบราว์เซอร์สำหรับเครื่องกำเนิดไฟฟ้านั้น จำกัด อยู่ที่ Firefox และ Chrome, AFAIK


3
คุณช่วยฉันไว้. $ .Deferred () คือสิ่งที่ฉันกำลังทำอยู่ ขอบคุณ
Temitayo

@noseratio ฉันลองสิ่งนี้ แต่เมธอด waitForIt จะไม่ถูกเรียกใช้เลย ผมทำอะไรผิดหรือเปล่า?
vigamage

@vigamage คุณสามารถให้ลิงค์ไปยัง jsfiddle หรือ codepen ของสิ่งที่คุณได้ลองไปแล้วได้ไหม?
noseratio

1
สวัสดีขอขอบคุณสำหรับความกังวลของคุณ ฉันทำให้มันทำงานได้ ขอบคุณ..!
vigamage

18

วิธีที่สวยงามในการรอฟังก์ชั่นหนึ่งให้เสร็จสมบูรณ์ก่อนคือการใช้สัญญากับฟังก์ชั่นasync / รอ


  1. ประการแรกสร้างสัญญา ฟังก์ชั่นที่ฉันสร้างจะเสร็จสมบูรณ์หลังจาก 2 วินาที ฉันใช้ setTimeoutเพื่อแสดงให้เห็นถึงสถานการณ์ที่คำแนะนำจะใช้เวลาในการดำเนินการ
  2. สำหรับฟังก์ชั่นที่สองคุณสามารถใช้ ฟังก์ชั่นasync / คอยซึ่งคุณจะawaitให้ฟังก์ชั่นแรกให้เสร็จสมบูรณ์ก่อนที่จะดำเนินการตามคำแนะนำ

ตัวอย่าง:

    //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()


บันทึก:

คุณสามารถเพียงแค่โดยไม่ค่าใด ๆ เช่นดังนั้น ในตัวอย่างของฉันฉันใช้ค่าที่ฉันสามารถใช้ในฟังก์ชั่นที่สองresolvePromiseresolve()resolvedPromisey


ใช่สิ่งนี้จะได้ผลเสมอ หากใครก็ตามที่เกี่ยวข้องกับสถานการณ์ประเภทนี้สัญญา JavaScript จะทำเคล็ดลับสำหรับพวกเขา
Puttamarigowda MS

5

ปัญหาเดียวของสัญญาคือ IE ไม่รองรับพวกเขา Edge ทำ แต่มี IE 10 และ 11 มากมาย: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (ความเข้ากันได้ที่ด้านล่าง)

ดังนั้น JavaScript จึงเป็นเธรดเดียว หากคุณไม่ได้ทำการโทรแบบอะซิงโครนัสมันจะทำงานตามที่คาดการณ์ไว้ เธรด JavaScript หลักจะเรียกใช้ฟังก์ชันหนึ่งอย่างสมบูรณ์ก่อนที่จะเรียกใช้งานฟังก์ชันถัดไปตามลำดับที่ปรากฏในรหัส ลำดับการรับประกันสำหรับฟังก์ชั่นซิงโครนัสเป็นเรื่องเล็กน้อย - แต่ละฟังก์ชั่นจะดำเนินการอย่างสมบูรณ์ตามลำดับที่ถูกเรียก

คิดว่าการทำงานของซิงโครเป็นหน่วยงานอะตอม เธรด JavaScript หลักจะดำเนินการอย่างสมบูรณ์ตามลำดับคำสั่งที่ปรากฏในรหัส

แต่ให้โทรแบบอะซิงโครนัสเหมือนในสถานการณ์ต่อไปนี้:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

นี้ไม่ได้ทำในสิ่งที่คุณต้องการ มันจะเรียกใช้งานฟังก์ชั่น 1, ฟังก์ชั่น 2, และฟังก์ชั่น 3 ทันทีการโหลดแฟลชวูบวาบและมันหายไปในขณะที่การโทร ajax ยังไม่เกือบสมบูรณ์แม้ว่าจะmakeAjaxCall()กลับมาแล้วก็ตาม ข้อสรุปคือการที่makeAjaxCall()แบ่งงานของมันออกเป็นชิ้น ๆ ซึ่งมีความก้าวหน้าทีละเล็กละน้อยโดยการหมุนแต่ละครั้งของเธรด JavaScript หลัก - มันมีพฤติกรรมแบบ asychronously แต่ในเธรดหลักเดียวกันนั้นในระหว่างการหมุนหนึ่งครั้ง / วิ่งจะดำเนินการส่วนซิงโครนัสอย่างรวดเร็วและคาดการณ์ได้

ดังนั้นวิธีที่ฉันจัดการ : เช่นฉันบอกว่าฟังก์ชั่นนี้เป็นหน่วยของงานปรมาณู ฉันรวมรหัสของฟังก์ชัน 1 และ 2 - ฉันใส่รหัสของฟังก์ชัน 1 ในฟังก์ชัน 2 ก่อนการเรียก asynch ฉันได้ยกเลิกฟังก์ชั่น 1. ทุกอย่างไปจนถึงและรวมถึงการเรียกแบบอะซิงโครนัสดำเนินการตามที่คาดการณ์ไว้

จากนั้นเมื่อเสร็จสิ้นการโทรไม่ตรงกันหลังจากหลายสปินของด้าย JavaScript หลักมีมันเรียกใช้ฟังก์ชัน 3. การค้ำประกันนี้การสั่งซื้อ ตัวอย่างเช่นด้วย ajax ตัวจัดการเหตุการณ์ onreadystatechange เรียกว่าหลายครั้ง เมื่อรายงานว่าเสร็จสมบูรณ์แล้วให้เรียกฟังก์ชันสุดท้ายที่คุณต้องการ

ฉันเห็นด้วยว่ามันยุ่งกว่า ฉันชอบมีโค้ดที่สมมาตรฉันชอบที่มีฟังก์ชั่นทำสิ่งหนึ่ง (หรือใกล้กับมัน) และฉันไม่ชอบการโทร ajax ในทางใดทางหนึ่งจะต้องรับผิดชอบต่อการแสดงผล (สร้างการพึ่งพาผู้โทร) แต่ด้วยการเรียกแบบอะซิงโครนัสที่ฝังอยู่ในฟังก์ชันซิงโครนัสการประนีประนอมจะต้องทำเพื่อรับประกันลำดับการดำเนินการ และฉันต้องโค้ดสำหรับ IE 10 ดังนั้นจึงไม่มีสัญญา

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

สำหรับการอภิปรายหัวข้อ JavaScript โปรดดู: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23และhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

อีกคำถามที่คล้ายกันและได้รับความนิยมสูงในเรื่องนี้: ฉันจะเรียก 3 ฟังก์ชั่นเพื่อดำเนินการอย่างใดอย่างหนึ่งหลังจากที่อื่นได้อย่างไร


8
สำหรับผู้ลงคะแนนฉันอยากรู้ว่ามีอะไรผิดปกติกับคำตอบนี้
มิต

0

นี่คือสิ่งที่ฉันคิดขึ้นมาเพราะฉันต้องทำงานหลายอย่างในสาย

<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>

โปรดเพิ่มความคิดเห็นระดับสูงอย่างน้อยก็เพื่อคำอธิบายของตัวอย่างที่กำหนด นี่คือเพื่ออธิบายว่ารหัสของคุณแก้ไขปัญหาได้อย่างไร
Cerberus เย็น

ปัญหาคือการเรียกใช้ฟังก์ชั่นที่สองหลังจากฟังก์ชั่นแรก ฉันแสดงวิธีเรียกใช้ฟังก์ชั่นที่สองหลังจากฟังก์ชั่นแรกและวิธีเรียกใช้ฟังก์ชั่นที่สามหลังจากฟังก์ชั่นที่สอง ฉันทำสามฟังก์ชั่นเพื่อให้ง่ายต่อการเข้าใจว่าต้องใช้รหัสใด
Niclas Arnberger

0

ความสนุกหลักของคุณจะโทรมาหาก่อนแล้วค่อยสนุกต่อไป

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');
            });
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.