Bagaimana saya bisa membuat fungsi Asynchronous di Javascript?

143

Lihat kode ini :

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

Seperti yang Anda lihat di konsol, fungsi "animate" asinkron, dan "fork" adalah alur kode blok pengendali event. Faktanya :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

ikuti aliran kode blok!

Jika saya ingin membuat function asyncFunct() { }perilaku saya dengan ini, bagaimana saya bisa melakukannya dengan javascript / jquery? Saya pikir ada strategi tanpa menggunakan setTimeout()

markzzz
sumber
lihat sumber jQuery :)
yatskevich
Mathod .animate () menggunakan panggilan balik. Animate akan memanggil panggilan balik ketika animasi selesai. Jika Anda memerlukan perilaku yang sama .animate () yang Anda butuhkan adalah panggilan balik (dipanggil oleh fungsi "utama" setelah beberapa operasi lainnya). Ini berbeda jika Anda memerlukan fungsi async "penuh" (fungsi yang disebut withouth memblokir aliran eksekusi). Dalam hal ini Anda dapat menggunakan setTimeout () dengan penundaan 0 dekat.
Fabio Buda
@Fabio Buda: mengapa callback () harus mengimplementasikan semacam async? Bahkan, itu tidak jsfiddle.net/5H9XT/9
markzzz
sebenarnya setelah "callback" saya mengutip metode async "penuh" dengan setTimeout. Maksud saya callback sebagai pseudo-async dalam cara fungsi dipanggil setelah kode lain :-)
Fabio Buda

Jawaban:

185

Anda tidak dapat membuat fungsi asinkron yang benar-benar tersuai. Anda akhirnya harus memanfaatkan teknologi yang disediakan secara asli, seperti:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Beberapa HTML5 API seperti File API, Web Database API
  • Teknologi yang mendukung onload
  • ... banyak lainnya

Bahkan, untuk animasi jQuery menggunakan setInterval .

pimvdb
sumber
1
Saya mendiskusikan ini dengan seorang teman kemarin jadi jawaban ini sangat cocok! Saya mengerti dan dapat mengidentifikasi fungsi async dan menggunakannya dalam JS dengan benar. Tapi kenapa kita tidak bisa menerapkan yang kustom tidak jelas bagi saya. Ini seperti kotak hitam yang kita tahu bagaimana membuatnya bekerja (menggunakan, katakanlah, setInterval) tetapi kita bahkan tidak bisa membukanya untuk melihat bagaimana hal itu dilakukan. Apakah Anda memiliki lebih banyak informasi tentang masalah ini?
Matheus Felipe
2
@MatheusFelipe fungsi-fungsi tersebut adalah asli dari implementasi mesin javascript dan satu-satunya hal yang dapat Anda andalkan adalah spesifikasi, mis. Penghitung waktu HTML5 dan percayai sifat kotak hitam yang berperilaku sesuai dengan spesifikasi.
Spoike
10
@MatheusFelipe youtu.be/8aGhZQkoFbQ bicara terbaik sejauh ini mengenai topik ini ...
Andreas Niedermair
Beberapa implemantations, khususnya Node.js, mendukungsetImmediate
Jon Surrell
bagaimana dengan promises. Apakah itu memberi awaitable?
Nithin Chandran
69

Anda dapat menggunakan timer:

setTimeout( yourFn, 0 );

(di mana yourFnreferensi ke fungsi Anda)

atau, dengan Lodash :

_.defer( yourFn );

Defers memohon funcsampai tumpukan panggilan saat ini telah dihapus. Argumen tambahan diberikan funcketika diminta.

Šime Vidas
sumber
3
Ini tidak berfungsi, fungsi javascript saya yang menggambar di kanvas terus membuat UI tidak merespons.
gab06
2
@ gab06 - Saya akan mengatakan fungsi menggambar kanvas Anda memblokir karena alasannya sendiri. Membagi aksinya menjadi banyak yang lebih kecil dan memanggil mereka masing-masing dengan timer: Anda akan melihat bahwa antarmuka dengan cara ini merespons klik mouse Anda, dll.
Marco Faustinelli
Tautan rusak.
JulianSoto
1
@JulianSoto diperbaiki
Šime Vidas
1
Waktu minimum setTimeoutadalah 4 milidetik oleh HTML5 spec. Memberinya 0 masih akan mengambil jumlah waktu minimum itu. Tapi ya, itu berfungsi dengan baik sebagai penunda fungsi.
hegez
30

di sini Anda memiliki solusi sederhana (tulis lain tentang itu) http://www.benlesh.com/2012/05/calling-javascript-function.html

Dan di sini Anda memiliki solusi siap di atas:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

TEST 1 ( dapat menampilkan '1 x 2 3' atau '1 2 x 3' atau '1 2 3 x' ):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

TEST 2 ( akan selalu menampilkan 'x 1' ):

async(function() {console.log('x');}, function() {console.log(1);});

Fungsi ini dijalankan dengan batas waktu 0 - ini akan mensimulasikan tugas asinkron

fider
sumber
6
TEST 1 sebenarnya hanya dapat menampilkan '1 2 3 x' dan TEST 2 dijamin untuk menghasilkan '1 x' setiap saat. Alasan untuk hasil tak terduga dalam TEST 2 adalah karena console.log(1)dipanggil dan output ( undefined) diteruskan sebagai argumen ke-2 async(). Dalam kasus TEST 1, saya pikir Anda tidak sepenuhnya memahami antrian eksekusi JavaScript. Karena setiap panggilan console.log()terjadi dalam tumpukan yang sama, xdijamin akan dicatat terakhir. Saya akan memilih jawaban ini untuk informasi yang salah tetapi tidak memiliki cukup perwakilan.
Joshua Piccari
1
@Joshua: Tampaknya @fider dimaksudkan untuk menulis TES 2 sebagai: async(function() {console.log('x')}, function(){console.log(1)});.
nzn
Ya @nzn dan @Joshua yang saya maksud TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- saya sudah
memperbaikinya
Output TEST 2 adalah 1 x dalam async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen
10

Berikut adalah fungsi yang mengambil fungsi lain dan menampilkan versi yang menjalankan async.

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

Ini digunakan sebagai cara sederhana untuk membuat fungsi async:

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

Ini berbeda dari jawaban @ fider karena fungsi itu sendiri memiliki struktur sendiri (tidak ada panggilan balik ditambahkan, itu sudah ada dalam fungsi) dan juga karena ia menciptakan fungsi baru yang dapat digunakan.

Ethan McTague
sumber
setTimeout tidak dapat digunakan dalam satu lingkaran (panggil fungsi yang sama beberapa kali dengan argumen yang berbeda) .
user2284570
@ user2284570 Itulah gunanya penutupan. (function(a){ asyncFunction(a); })(a)
Putar
1
IIRC, Anda juga bisa mencapai ini tanpa penutupan:setTimeout(asyncFunction, 0, a);
Putar
1
Jika dengan async yang kami maksud: berjalan di latar belakang, sejajar dengan utas utama maka ini tidak benar-benar asink. Semua ini akan dilakukan adalah menunda eksekusi ke process.nextTick. Kode apa pun yang Anda miliki dalam fungsi akan dieksekusi pada utas utama. Jika fungsi diatur untuk menghitung PI maka aplikasi akan membeku, dengan atau tanpa batas waktu!
Mike M
1
Saya tidak mengerti mengapa jawaban ini dibatalkan. Ketika saya memasukkan ini ke dalam kode saya, program memblokir sampai fungsi selesai, yang seharusnya tidak dilakukan.
Martin Argerami
6

Sunting: Saya benar-benar salah paham pertanyaannya. Di browser, saya akan menggunakan setTimeout. Jika penting menjalankannya di utas lain, saya akan menggunakan Pekerja Web .

Linus Thiel
sumber
1
? Ini tidak membuat fungsi async: O
markzzz
6

Terlambat, tetapi untuk menunjukkan solusi yang mudah digunakan promisessetelah diperkenalkan di ES6 , ini menangani panggilan asinkron jauh lebih mudah:

Anda menetapkan kode asinkron dalam janji baru:

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

Catatan untuk mengatur resolve()kapan panggilan async selesai.
Kemudian Anda menambahkan kode yang ingin Anda jalankan setelah panggilan async selesai di .then()dalam janji:

asyncFunct.then((result) => {
    console.log("Exit");    
});

Berikut ini cuplikannya:

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");            	
            resolve();
        }); 			
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

atau JSFiddle

hd84335
sumber
6

Halaman ini menuntun Anda melalui dasar-dasar membuat fungsi javascript async.

Sejak ES2017, fungsi javacript asinkron jauh lebih mudah untuk ditulis. Anda juga harus membaca lebih lanjut tentang Janji .

shanabus
sumber
Tautannya sudah mati.
Martin Argerami
3

Jika Anda ingin menggunakan Parameter dan mengatur jumlah fungsi async maksimum, Anda bisa menggunakan pekerja async sederhana yang saya buat:

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

Anda bisa menggunakannya seperti ini:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
João Alves
sumber
2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

Meskipun secara fundamental tidak berbeda dari solusi lain, saya pikir solusi saya melakukan beberapa hal baik tambahan:

  • memungkinkan untuk parameter ke fungsi
  • itu melewati output dari fungsi ke panggilan balik
  • itu ditambahkan untuk Function.prototypememungkinkan cara yang lebih baik untuk menyebutnya

Juga, kesamaan dengan fungsi Function.prototype.applybawaan sepertinya cocok untuk saya.

Benjamin Kuykendall
sumber
1

Di samping jawaban hebat oleh @pimvdb, dan untuk berjaga-jaga jika Anda bertanya-tanya, async.js juga tidak menawarkan fungsi yang benar-benar tidak sinkron. Berikut ini adalah versi (sangat) dari metode utama perpustakaan:

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

Jadi fungsi melindungi dari kesalahan dan dengan anggun menyerahkannya ke callback untuk ditangani, tetapi kode ini sama sinkronnya dengan fungsi JS lainnya.

Mike M
sumber
1

Sayangnya, JavaScript tidak menyediakan fungsionalitas async. Ini hanya berfungsi dalam satu utas tunggal. Tapi sebagian besar browser modern memberikan Workers , yang script kedua yang dijalankan di latar belakang dan dapat kembali hasilnya. Jadi, saya mencapai solusi yang menurut saya berguna untuk menjalankan fungsi secara tidak sinkron, yang menciptakan pekerja untuk setiap panggilan async.

Kode di bawah ini berisi fungsi async untuk memanggil di latar belakang.

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

Ini adalah contoh untuk penggunaan:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

Ini mengeksekusi fungsi yang iterasi a fordengan jumlah yang sangat besar untuk mengambil waktu sebagai demonstrasi asinkronisitas, dan kemudian mendapatkan dua kali lipat dari angka yang dilewati. The asyncMetode menyediakan functionyang memanggil fungsi yang diinginkan di latar belakang, dan di mana yang disediakan sebagai parameter asynccallback yang returndalam parameter yang unik. Jadi dalam fungsi callback saya alerthasilnya.

Davide Cannizzo
sumber
0

MDN memiliki contoh yang baik tentang penggunaan setTimeout yang mempertahankan "ini".

Seperti yang berikut ini:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
sumber