Cara yang tepat untuk menunggu satu fungsi selesai sebelum melanjutkan?

187

Saya memiliki dua fungsi JS. Yang satu memanggil yang lain. Di dalam fungsi panggilan, saya ingin memanggil yang lain, menunggu sampai fungsi itu selesai, lalu melanjutkan. Jadi, misalnya / kode semu:

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

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

Saya datang dengan solusi ini, tetapi tidak tahu apakah ini cara yang cerdas untuk melakukannya.

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

Apakah itu sah? Apakah ada cara yang lebih elegan untuk menanganinya? Mungkin dengan jQuery?

DA.
sumber
11
Apa firstFunctionsebenarnya yang membuatnya tidak sinkron? Either way - periksa tentang janji
zerkms
Fungsi pertama dengan cepat memperbarui skor jam setiap 10 detik. Yang ... kalau dipikir-pikir, saya kira kita bisa pra-menghitung dan kemudian hanya secara manual menjeda panggilan fungsi kedua melalui setTimeout. Yang mengatakan, saya masih bisa melihat keinginan untuk memiliki kemampuan jeda di tempat lain.
DA.
Anda tidak perlu jeda - google untuk janji di jquery
zerkms
Janji @zerkms terlihat menarik! Masih menyelidiki dukungan browser ...
DA.
1
Saya juga punya masalah yang sama.
Vappor Washmade

Jawaban:

141

Salah satu cara untuk menangani pekerjaan asinkron seperti ini adalah dengan menggunakan fungsi panggilan balik, misalnya:

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

Sesuai saran @Janaka Pushpakumara, Anda sekarang dapat menggunakan fungsi panah untuk mencapai hal yang sama. Sebagai contoh:

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


Pembaruan: Saya menjawab ini beberapa waktu yang lalu, dan benar-benar ingin memperbaruinya. Sementara panggilan balik benar-benar baik-baik saja, dalam pengalaman saya mereka cenderung menghasilkan kode yang lebih sulit dibaca dan dipelihara. Ada situasi di mana saya masih menggunakannya, seperti untuk meneruskan dalam acara kemajuan dan sejenisnya sebagai parameter. Pembaruan ini hanya untuk menekankan alternatif.

Juga pertanyaan aslinya tidak secara spesifik menyebutkan async, jadi kalau-kalau ada yang bingung, jika fungsi Anda sinkron, itu akan diblokir ketika dipanggil. Sebagai contoh:

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

Jika meskipun tersirat, fungsinya asinkron, cara saya cenderung menangani semua pekerjaan asinkron saya hari ini adalah dengan async / menunggu. Sebagai contoh:

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

IMO, async / menunggu membuat kode Anda jauh lebih mudah dibaca daripada menggunakan janji secara langsung (sebagian besar waktu). Jika Anda perlu menangani kesalahan penangkapan maka gunakan dengan mencoba / menangkap. Baca lebih lanjut di sini: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function .

Matt Way
sumber
9
@zerkms - Ingin menguraikan?
Matt Way
7
panggilan balik tidak nyaman untuk berurusan dengan asinkron, janji jauh lebih mudah dan fleksibel
zerkms
1
Meskipun Anda benar bahwa saya seharusnya tidak menggunakan kata terbaik (diperbarui), kenyamanan panggilan balik vs janji tergantung pada kompleksitas masalah.
Matt Way
3
Itu menjadi offtopic, tapi saya pribadi tidak melihat alasan untuk memilih callback hari ini :-) Tidak dapat mengingat kode saya yang ditulis dalam 2 tahun terakhir memberikan callback lebih dari janji.
zerkms
3
Jika Anda mau, Anda dapat menggunakan fungsi panah di es6 secondFunction () {firstFunction ((response) => {console.log (response);}); }
Janaka Pushpakumara
53

Gunakan async / tunggu:

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

lalu gunakan menunggu di fungsi Anda yang lain untuk menunggu kembali:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};
Fawaz
sumber
9
bagi kita yang terjebak mendukung browser yang lebih lama, IE tidak mendukung async / menunggu
kyle
Apakah ini dapat digunakan di luar kedua fungsi, seperti pada $(function() { await firstFunction(); secondFunction(); });,?
Lewistrick
1
@Lewistrick Ingatlah awaithanya dapat digunakan di dalam suatu asyncmetode. Jadi, jika Anda membuat fungsi orang tua, asyncAnda dapat memanggil sebanyak mungkin orang async methodsdi dalam dengan atau tanpa awaityang Anda inginkan.
Fawaz
Apakah mungkin untuk awaitsuatu setTimeout?
Shayan
1
@Shayan ya, bungkus setTimeout di fungsi lain yang menyelesaikan janji setelah waktu habis.
Fawaz
51

Tampaknya Anda melewatkan satu poin penting di sini: JavaScript adalah lingkungan eksekusi berulir tunggal. Mari kita lihat kembali kode Anda, perhatikan saya telah menambahkan 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
        };
    }
};

Anda tidak harus menunggu isPaused. Ketika Anda melihat peringatan "Di Sini", isPausedakan falsesudah, dan firstFunctionakan kembali. Itu karena Anda tidak dapat "menghasilkan" dari dalam forloop ( // do something), loop mungkin tidak terputus dan harus sepenuhnya menyelesaikan terlebih dahulu (lebih detail: Javascript thread-handling dan ras-conditions ).

Yang mengatakan, Anda masih dapat membuat aliran kode di dalam firstFunctionmenjadi asinkron dan menggunakan panggilan balik atau janji untuk memberi tahu penelepon. Anda harus menyerah pada forloop dan mensimulasikannya sebagai ifgantinya ( 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);
    });
}

Sebagai tambahan, JavaScript 1.7 telah memperkenalkan yieldkata kunci sebagai bagian dari generator . Itu akan memungkinkan untuk "meninju" lubang asinkron dalam aliran kode JavaScript yang sebaliknya sinkron ( lebih detail dan contoh ). Namun, dukungan browser untuk generator saat ini terbatas untuk Firefox dan Chrome, AFAIK.

noseratio
sumber
3
Kamu menyelamatkanku. $ .Deferred () adalah apa yang saya inginkan. Terima kasih
Temitayo
@noseratio Saya mencoba ini. Tetapi metode waitForIt tidak dipanggil sama sekali. Apa yang saya lakukan salah?
vigamage
@vigamage, dapatkah Anda memberikan tautan ke jsfiddle atau codepen dari apa yang Anda coba?
noseratio
1
Halo, Terima kasih atas perhatian Anda. Saya membuatnya bekerja. Terima kasih..!
vigamage
18

Cara elegan untuk menunggu satu fungsi selesai terlebih dahulu adalah menggunakan Janji dengan fungsi async / menunggu .


  1. Pertama, buat Janji . Fungsi yang saya buat akan selesai setelah 2s. Saya menggunakan setTimeoutuntuk menunjukkan situasi di mana instruksi akan memakan waktu untuk dieksekusi.
  2. Untuk fungsi kedua, Anda dapat menggunakan fungsi async / await di mana Anda akan awaitmenyelesaikan fungsi pertama sebelum melanjutkan dengan instruksi.

Contoh:

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


catatan:

Anda hanya bisa resolveyang Promisetanpa nilai apapun seperti begitu resolve(). Dalam contoh saya, saya resolvedyang Promisedengan nilai yyang saya kemudian dapat digunakan dalam fungsi kedua.

Jakub A Suplicki
sumber
ya ini akan selalu berhasil. jika ada yang terlibat dalam skenario semacam ini maka JavaScript Promise akan melakukan trik untuk mereka.
Puttamarigowda MS
5

Satu-satunya masalah dengan janji adalah bahwa IE tidak mendukungnya. Edge melakukannya, tetapi ada banyak IE 10 dan 11 di luar sana: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (kompatibilitas di bagian bawah)

Jadi, JavaScript adalah utas tunggal. Jika Anda tidak melakukan panggilan asinkron, itu akan berperilaku terprediksi. Utas JavaScript utama akan menjalankan satu fungsi sepenuhnya sebelum menjalankan yang berikutnya, sesuai urutan yang muncul dalam kode. Jaminan pesanan untuk fungsi sinkron adalah sepele - setiap fungsi akan dieksekusi sepenuhnya dalam urutan yang dipanggil.

Pikirkan fungsi sinkron sebagai unit kerja atom . Utas JavaScript utama akan menjalankannya sepenuhnya, sesuai dengan urutan pernyataan yang muncul dalam kode.

Tapi, serahkan panggilan asinkron, seperti dalam situasi berikut:

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

Ini tidak melakukan apa yang Anda inginkan . Ini secara instan mengeksekusi fungsi 1, fungsi 2, dan fungsi 3. Memuat div berkedip dan hilang, sementara panggilan ajax hampir tidak lengkap, meskipun makeAjaxCall()telah kembali. KOMPLIKASI yang makeAjaxCall()telah memecah pekerjaannya menjadi potongan-potongan yang maju sedikit demi sedikit oleh setiap putaran utas JavaScript utama - itu berperilaku asikronik. Tetapi utas utama yang sama, selama satu putaran / putaran, mengeksekusi bagian sinkron dengan cepat dan dapat diprediksi.

Jadi, cara saya menanganinya : Seperti saya katakan fungsinya adalah unit atom. Saya menggabungkan kode fungsi 1 dan 2 - saya meletakkan kode fungsi 1 di fungsi 2, sebelum panggilan asynch. Saya menyingkirkan fungsi 1. Semuanya hingga dan termasuk panggilan asinkron dijalankan secara terprediksi, secara berurutan.

LALU, ketika panggilan asinkron selesai, setelah beberapa putaran utas JavaScript utama, memilikinya fungsi panggil 3. Ini menjamin pesanan . Misalnya, dengan ajax, pengendali event onreadystatechange disebut beberapa kali. Ketika melaporkannya selesai, maka panggil fungsi akhir yang Anda inginkan.

Saya setuju itu berantakan. Saya suka memiliki kode menjadi simetris, saya suka memiliki fungsi melakukan satu hal (atau dekat dengan itu), dan saya tidak suka memiliki panggilan ajax dengan cara apa pun bertanggung jawab atas tampilan (membuat ketergantungan pada penelepon). NAMUN, dengan panggilan asinkron yang tertanam dalam fungsi sinkron, kompromi harus dilakukan untuk menjamin urutan eksekusi. Dan saya harus kode untuk IE 10 jadi tidak ada janji.

Rangkuman : Untuk panggilan sinkron, jaminan pesanan itu sepele. Setiap fungsi dieksekusi sepenuhnya dalam urutan yang disebutnya. Untuk fungsi dengan panggilan asinkron, satu-satunya cara untuk menjamin pesanan adalah dengan memonitor ketika panggilan async selesai, dan memanggil fungsi ketiga saat kondisi itu terdeteksi.

Untuk diskusi tentang utas JavaScript, lihat: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 dan https://developer.mozilla.org/en-US/docs/Web/JavaScript/ EventLoop

Juga, pertanyaan serupa lainnya yang berperingkat tinggi tentang hal ini: Bagaimana saya harus memanggil 3 fungsi untuk menjalankannya satu demi satu?

kermit
sumber
8
Kepada para downvoter, saya ingin tahu apa yang salah dengan jawaban ini.
kermit
0

Ini yang saya pikirkan, karena saya perlu menjalankan beberapa operasi dalam sebuah rantai.

<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>
Niclas Arnberger
sumber
Harap tambahkan setidaknya jus komentar tingkat tinggi untuk deskripsi cuplikan yang diberikan. Ini untuk menjelaskan bagaimana kode Anda mengatasi masalah.
Cold Cerberus
Masalahnya adalah menjalankan fungsi kedua setelah yang pertama. Saya menunjukkan bagaimana menjalankan fungsi kedua setelah yang pertama, dan juga bagaimana menjalankan fungsi ketiga setelah yang kedua. Saya membuat tiga fungsi, untuk memudahkan memahami kode apa yang diperlukan.
Niclas Arnberger
0

Kegembiraan utama Anda akan memanggil firstFun kemudian selesaikan kesenangan Anda berikutnya akan memanggil

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');
            });
        }
Gajender Singh
sumber