Eşzamansız Bir Eşzamansız Javascript İşlevini Çağırma


222

Birincisi, bu, eşzamansız bir çağrıyı binlerce satır uzunluğunda ve zamanın şu anda değişiklik yapma yeteneğini karşılamayan çok eşzamanlı bir kod tabanına uyarlamayı kasıtlı olarak yanlış yapmanın çok özel bir durumudur. O doğru." Varlığımın her lifini incitir, ancak gerçeklik ve idealler genellikle birbirine geçmez. Bunun berbat olduğunu biliyorum.

Tamam, yolumdan, bunu nasıl yapabilirim ki:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

Örneklerin tümü (veya bunların eksikliği), her ikisi de bu çözüm için uygun olmayan kütüphaneleri ve / veya derleyicileri kullanır. UI donmadan OLMAK (nasıl geri arama çağrılıncaya kadar doSomething işlevini bırakmayın) nasıl blok yapmak için somut bir örnek gerekir. JS'de böyle bir şey mümkünse.


16
Bir tarayıcı bloğu yapmak ve beklemek mümkün değildir. Sadece yapmayacaklar.
Sivri

2
çoğu tarayıcıda engelleme mekanizmalarına sahip javascript dozajı ... zaman uyumsuz çağrı verileri döndürmek için bittiğinde çağrılan bir geri arama oluşturmak isteyeceksiniz
Nadir Muzaffar

8
Tarayıcıya "Bu önceki işlevi zaman uyumsuz olarak çalıştırmanızı söylemiştim, ama gerçekten öyle demek istemedim!" Bunun mümkün olmasını neden beklesin ki?
Wayne

2
Düzenleme için Dan'a teşekkürler. Kesinlikle kaba değildim, ama ifadelerin daha iyi.
Robert C. Barth

2
@ RobertC.Barth Artık JavaScript ile de mümkün. asenkron bekleme işlevleri henüz standartta onaylanmadı, ancak ES2017'de olması planlanıyor. Daha fazla ayrıntı için aşağıdaki cevabıma bakın.
John

Yanıtlar:


135

"bana bunu nasıl" doğru şekilde "yapmam gerektiğini söyleme"

TAMAM. ama gerçekten doğru şekilde yapmalısın ... ya da her neyse

"Nasıl engelleneceği konusunda somut bir örneğe ihtiyacım var ... Kullanıcı arayüzünü dondurmadan. JS'de böyle bir şey mümkünse."

Hayır, çalışan JavaScript'i kullanıcı arayüzünü engellemeden engellemek imkansızdır.

Bilgi eksikliği göz önüne alındığında, bir çözüm sunmak zordur, ancak bir seçenek, çağrı işlevinin genel bir değişkeni kontrol etmek için bazı yoklama yapması, ardından geri çağrıyı datagenel olarak ayarlaması olabilir.

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

Tüm bunlar değiştirebileceğiniz varsayılmaktadır doSomething(). Kartlarda olup olmadığını bilmiyorum.

Değiştirilebiliyorsa, neden doSomething()diğer geri aramalardan aranacak bir geri aramayı neden geçmeyeceğinizi bilmiyorum , ama başım belaya girmeden önce dursam iyi olur. ;)


Oh, ne halt. Doğru yapılabileceğini gösteren bir örnek verdiniz, bu yüzden bu çözümü göstereceğim ...

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});

Örneğinizde, zaman uyumsuz çağrıya iletilen bir geri arama içerdiğinden, doğru yol geri aramadan çağrılacak bir işlevi geçirmektir doSomething().

Elbette geri aramanın yaptığı tek şey buysa, funcdoğrudan geçersiniz ...

myAsynchronousCall(param1, func);

22
Evet, doğru bir şekilde nasıl yapılacağını biliyorum, belirtilen özel nedenden dolayı nasıl yapılacağını / yapılamayacağını bilmem gerekiyor. Benim asıl, myAsynchronousCall geri arama işlevi çağrısını tamamlayana kadar doSomething () bırakmak istemiyorum. Bleh, bu yapılamaz, şüphelendiğim gibi, sadece beni desteklemek için İnternetlerin toplanan bilgeliğine ihtiyacım vardı. Teşekkür ederim. :-)
Robert C. Barth

2
@ RobertC.Barth: Evet, şüpheleriniz maalesef doğruydu.

Ben mi yoksa sadece "doğru şekilde yapıldı" sürümü çalışıyor mu? Soru, geri dönüş çağrısı içeriyordu, daha önce async çağrısının bitmesini bekleyen, bu cevabın bu ilk kısmının kapsamadığı bir şey
olmalı

@ravemir: Cevap istediği şeyi yapmanın mümkün olmadığını belirtir . Bu anlaşılması gereken önemli kısım. Başka bir deyişle, kullanıcı arayüzünü engellemeden eşzamansız arama yapamaz ve değer döndüremezsiniz. Bu nedenle ilk çözüm, global bir değişken kullanan ve bu değişkenin değiştirilip değiştirilmediğini görmek için yoklama kullanan çirkin bir hack'tir. İkinci sürüm doğru yoldur.

1
@Leonardo: Bu soruda gizemli işlev çağrılıyor. Temel olarak, kodu eşzamansız olarak çalıştıran ve alınması gereken bir sonuç üreten her şeyi temsil eder. Yani bir AJAX isteği gibi olabilir. Geçmek callbackiçin işlevini myAsynchronousCallkendi zaman uyumsuz şeyler yapar ve tam zaman geri çağırır fonksiyonu. İşte bir demo.

60

ES2017'deki bir özellik olan zaman uyumsuz işlevler , vaatleri (belirli bir zaman uyumsuz kod biçimi) ve awaitanahtar kelimeyi kullanarak zaman uyumsuz kodun senkronize görünmesini sağlar . Ayrıca, anahtar kelimenin asyncönündeki functionasenkron / bekleme işlevini gösteren anahtar kelimenin altındaki kod örneklerinde de dikkat edin . awaitAnahtar kelime ile önceden sabitlenmiş bir işlevde olmadan çalışmaz asyncanahtar kelime. Şu anda bunun bir istisnası olmadığından hiçbir üst düzey beklemenin işe yaramayacağı anlamına gelir (üst düzey herhangi bir işlevin dışında beklemek anlamına gelir). Üst düzeyawait bir teklif olsa da .

ES2017, 27 Haziran 2017'de JavaScript için standart olarak onaylandı (yani sonlandırıldı). Async bekliyor tarayıcınızda zaten çalışıyor olabilir, ancak değilse, babel veya traceur gibi bir javascript transpiler kullanarak işlevselliği kullanmaya devam edebilirsiniz . Chrome 55, zaman uyumsuz işlevler için tam desteğe sahiptir. Bu nedenle, daha yeni bir tarayıcınız varsa aşağıdaki kodu deneyebilirsiniz.

Bkz kangax en es2017 uyumluluk tablosunu tarayıcı uyumluluğu için.

Aşağıda doAsync, üç saniyelik bir duraklama alan ve başlangıç ​​zamanından sonraki her bir duraklamadan sonraki zaman farkını basan bir zaman uyumsuz bekleme işlevi verilmiştir :

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();

Await anahtar sözcüğü bir vaat değerinin önüne yerleştirildiğinde (bu durumda vaat değeri doSomethingAsync işlevi tarafından döndürülen değerdir) await anahtar sözcüğü işlev çağrısının yürütülmesini duraklatır, ancak diğer işlevleri duraklatmaz ve devam eder vaat çözülünceye kadar diğer kodların yürütülmesi. Söz çözüldükten sonra sözün değerini açacaktır ve beklemek ve söz ifadesinin yerine bu kaydırılmamış değerin yerini aldığını düşünebilirsiniz.

Bu nedenle, beklemeleri duraklatmaları beklediğinden, satırın geri kalanını yürütmeden önce bir değeri çözdüğünden, bir dizide beklenen zaman farklılıklarını toplayan ve diziyi basan aşağıdaki örnekteki gibi döngüler ve iç işlev çağrıları için kullanabilirsiniz.

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})

Async işlevinin kendisi bir söz verir, böylece bunu yukarıda veya başka bir async bekliyor işlevinin içinde yaptığım gibi zincirleme ile bir söz olarak kullanabilirsiniz.

İstekleri aynı anda göndermek isterseniz, yukarıdaki işlev başka bir istek göndermeden önce her yanıtı beklerdi Promise.all kullanabilirsiniz .

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})

Söz muhtemelen reddederse, bir try catch'e sarabilir veya try catch'i atlayabilir ve hatanın async / await fonksiyonlarının catch çağrısına yayılmasına izin verebilirsiniz. Özellikle Node.js'de vaat hatalarını işlenmemeye dikkat etmelisiniz. Aşağıda, hataların nasıl çalıştığını gösteren bazı örnekler verilmiştir.

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)

Gidersen burada yaklaşan ECMAScript sürümleri için bitmiş önerilerini görebilirsiniz.

Sadece ES2015 (ES6) ile kullanılabilen buna bir alternatif, bir jeneratör fonksiyonunu saran özel bir fonksiyon kullanmaktır. Jeneratör işlevleri, await anahtar sözcüğünü çevreleyen bir işlevle çoğaltmak için kullanılabilecek bir verim anahtar sözcüğüne sahiptir. Verim anahtar sözcüğü ve jeneratör işlevi çok daha genel bir amaçtır ve asenkronun beklediği fonksiyondan çok daha fazlasını yapabilir. Eşzamansız bekleyen çoğaltmak için kullanılabilecek bir jeneratör işlevi sarıcı istiyorsanız co.js göz atın . Bu arada ko'nun işlevi async'i beklemek gibi işlevler vaat ediyor. Dürüst olmak gerekirse, bu noktada tarayıcı uyumluluğu hem jeneratör işlevleri hem de zaman uyumsuz işlevler için yaklaşık olarak aynı olsa da, async işlevini beklemek istiyorsanız, Async işlevlerini co.js olmadan kullanmalısınız.

IE dışında tüm önemli tarayıcılarda (Chrome, Safari ve Edge) Async işlevleri (2017 itibariyle) için tarayıcı desteği aslında oldukça iyi.


2
Bu yanıtı beğendim
ycomp

1
ne kadar uzağa geldik :)
Derek

3
Bu harika bir yanıt, ancak orijinal posterler sorunu için, tek yaptığı sorunu bir seviye yukarı taşımaktır. Diyelim ki doSomething'i içeride bekleyen async fonksiyonuna dönüştürüyor. Bu işlev şimdi bir söz veriyor ve eşzamansız, bu nedenle o işlevi çağıran her şeyde aynı sorunla tekrar başa çıkmak zorunda kalacak.
dpwrussell

1
@dpwrussell bu doğrudur, kod tabanında zaman uyumsuz işlevlerin ve vaatlerin bir sürünme vardır. Her şeyi içinde sürünen gelen çözülmesine vaat etmenin en iyi yolu böyle son derece garip ve tartışmalı bir şey yapmak sürece eşzamanlı bir zaman uyumsuz değeri döndürmek için hiçbir yolu yoktur, sadece yazma senkron geri aramaları etmektir twitter.com/sebmarkbage/status/941214259505119232 Bilmiyorum önermek. Soruyu sorulduğu gibi daha eksiksiz cevaplamak ve sadece başlığı cevaplamak için sorunun sonuna bir düzenleme ekleyeceğim.
John

Bu harika bir cevap +1 ve hepsi, ama olduğu gibi yazılmış, bunun geri arama kullanmaktan daha az karmaşık olduğunu görmüyorum.
Altimus Prime

47

JQuery Promises'a bir göz atın:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

Kodu yeniden düzenleyin:

    var dfd = yeni jQuery.Deferred ();


    function callBack (data) {
       dfd.notify (veri);
    }

    // zaman uyumsuz çağrı yapın.
    myAsynchronousCall (param1, callBack);

    işlevi doSomething (data) {
     // verilerle şeyler yapın ...
    }

    , $ .Her (DFD) .o (doSomething);



3
Bu cevap için +1, bu doğru. Ancak ben çizgiyi güncelliyorduk dfd.notify(data)içindfd.resolve(data)
Jason

7
Bu kod aslında senkronize DEĞİL olmadan senkron olmak yanılsaması veren bir durum mudur?
saurshaz

2
vaat IMO sadece iyi organize geri aramalar :) Eğer bir eşzamanlı çağrı gerekiyorsa diyelim ki bazı nesne başlatma söz veriyorum biraz vaat ediyor.
webduvet

10
Vaatler senkronize değil.
Vans S

6

Bir güzel çözüm de yoktur http://taskjs.org/

Javascript için yeni jeneratörler kullanır. Bu yüzden şu anda çoğu tarayıcı tarafından uygulanmadı. Firefox'ta test ettim ve benim için asenkron işlevi sarmanın güzel bir yolu.

İşte GitHub projesinden örnek kod

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}

3

Sen edebilirsiniz ile senkron olması NodeJS içinde asenkron JavaScript zorlamak senkron-rpc .

Yine de UI'nizi kesinlikle dondurur, bu yüzden almanız gereken kısayolu almanın mümkün olup olmadığı konusunda hala bir naysayerim. NodeJS bazen engellemenize izin verse bile, One And Only Thread'ı JavaScript'te askıya almak mümkün değildir. Hiçbir geri arama, etkinlik, eşzamansız hiçbir şey vaat edilene kadar işleyemez. Bu nedenle, okuyucu OP gibi kaçınılmaz bir duruma sahip olmadıkça (veya benim durumumda, geri arama, etkinlik vb. Olmadan yüceltilmiş bir kabuk betiği yazmıyorsanız), bunu YAPMAYIN!

Ancak bunu nasıl yapabileceğiniz aşağıda açıklanmıştır:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

SINIRLAMA:

Bunlar hem nasıl bir sonucudur sync-rpckötüye gereğidir uygulanır, require('child_process').spawnSync:

  1. Bu tarayıcıda çalışmaz.
  2. İşlevinizle ilgili bağımsız değişkenler serileştirilebilir olmalıdır . Argümanlarınız içeri girip çıkıyor JSON.stringify, bu nedenle prototip zincirleri gibi işlevler ve numaralandırılamayan özellikler kaybolacak.

1

Ayrıca geri aramalara dönüştürebilirsiniz.

function thirdPartyFoo(callback) {    
  callback("Hello World");    
}

function foo() {    
  var fooVariable;

  thirdPartyFoo(function(data) {
    fooVariable = data;
  });

  return fooVariable;
}

var temp = foo();  
console.log(temp);

0

Şimdi istediğin aslında mümkün. Bir hizmet çalışanında eşzamansız kodu ve bir web çalışanında eşzamanlı kodu çalıştırabilirseniz, web çalışanının hizmet çalışanına eşzamanlı bir XHR göndermesini ve hizmet çalışanı zaman uyumsuz şeyleri yaparken, web çalışanının iş parçacığı bekleyecek. Bu harika bir yaklaşım değil, işe yarayabilir.


-4

Gereksinimi biraz değiştirirseniz elde etmeyi umduğunuz fikir mümkün olabilir

Çalışma zamanınız ES6 belirtimini destekliyorsa aşağıdaki kod mümkündür.

Zaman uyumsuz işlevler hakkında daha fazla bilgi

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}

4
Firefox hata veriyor: SyntaxError: await is only valid in async functions and async generators. Param1'in tanımlanmadığından (ve hatta kullanılmadığından) bahsetmiyorum bile.
Harvey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.