Süreklilik / geri aramalar ile kodu nasıl okunabilir halde tutarsınız?


11

Özet: Eşzamansız kod ve geri aramalar kullanmamıza rağmen kodumu okunabilir tutmak için izleyebileceğim iyi kurulmuş en iyi uygulama kalıpları var mı?


Eşzamansız ve yoğun geri çağrılara dayanan bir çok şey yapan bir JavaScript kitaplığı kullanıyorum. Basit bir "A yükü, B yükü, ..." yöntemini yazmak oldukça karmaşık ve izlenmesi zorlaşıyor.

Bir örnek vereyim. Diyelim ki uzak bir web sunucusundan bir grup görüntü (eşzamansız olarak) yüklemek istiyorum. C # / async'de şöyle bir şey yazardım:

disableStartButton();

foreach (myData in myRepository) {
    var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
    if (result.Success) {
        myData.Image = result.Data;
    } else {
        write("error loading Image " + myData.Id);
        return;
    }
}

write("success");
enableStartButton();

Kod düzeni "olayların akışını" takip eder: İlk olarak, başlat düğmesi devre dışı bırakılır, daha sonra görüntüler yüklenir ( awaitkullanıcı arayüzünün duyarlı kalmasını sağlar) ve ardından başlat düğmesi tekrar etkinleştirilir.

JavaScript'te, geri çağrıları kullanarak, bununla geldim:

disableStartButton();

var count = myRepository.length;

function loadImage(i) {
    if (i >= count) {
        write("success");
        enableStartButton();
        return;
    }

    myData = myRepository[i];
    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                return;
            }
            loadImage(i+1); 
        }
    );
}

loadImage(0);

Dezavantajları açık olduğunu düşünüyorum: Ben özyinelemeli bir çağrı içine döngü yeniden çalışmak zorunda kaldı, sonunda yürütülmesi gereken kod fonksiyonun ortasında bir yerde, indirme ( loadImage(0)) başlayan kod en altta, ve okumak ve takip etmek genellikle daha zordur. Çirkin ve hoşuma gitmedi.

Bu sorunla karşılaşan ilk kişi olmadığımdan eminim, bu yüzden sorum şu: Eşzamansız kod ve geri aramalar kullanmamıza rağmen kodumu okunabilir tutmak için takip edebileceğim iyi kurulmuş en iyi uygulama kalıpları var mı?


"Eşzamansız" çağrılarınızın sırayla yapılması için belirli bir neden var mı? Bu başka bir kodun basitleştirilmiş bir versiyonu mu?
Izkata

@Izkata: Nedeni sadece uzak sunucu için iyi olmak istedim (= yüzlerce eşzamanlı istek ile bombardıman değil). Taş olarak ayarlanmış bir gereklilik değildir. Evet, kodun basitleştirilmiş bir versiyonu, LoadImageAsyncaslında Ext.Ajax.requestSencha Touch'a bir çağrı .
Heinzi

1
Çoğu tarayıcı , sunucuyu yine de çekmenize izin vermez - sadece istekleri sıraya alır ve bir önceki tamamlandıktan sonra bir sonrakini başlatır.
12'de Izkata


Tanrı! çok kötü tavsiye burada. Hiçbir tasarım deseni size yardımcı olmayacaktır. İçine bak async.js , async.waterfallsenin cevaptır.
Salman von Abbas

Yanıtlar:


4

Düz js ile C # 5'in geri aramalarıyla çalışırken aynı özlü ve anlamlı düzeyde elde edebilmeniz pek olası değildir. Derleyici işi sizin için tüm bu ısıtıcı levhayı yazılı olarak yapar ve js çalışma zamanları bunu yapana kadar, burada ve ara sıra ara sıra geri dönmeniz gerekir.

Bununla birlikte, geri çağrıları her zaman doğrusal kodun sadeliğine getirmek istemeyebilirsiniz - fonksiyonları atmak çirkin olmak zorunda değildir, bu tür kodlarla çalışan tüm bir dünya vardır ve aklı başında asyncve olmadan aklı başında kalırlar await.

Örneğin, daha üst düzey işlevleri kullanın (benim js biraz paslı olabilir):

// generic - this is a library function
function iterateAsync(iterator, action, onSuccess, onFailure) {
var item = iterator();
if(item == null) { // exit condition
    onSuccess();
    return;
}
action(item,
    function (success) {
        if(success)
            iterateAsync(iterator, action, onSuccess, onFailure);
        else
            onFailure();
    });
}


// calling code
var currentImage = 0;
var imageCount = 42;

// you know your library function expects an iterator with no params, 
// and an async action with the current item and its continuation as params
iterateAsync(
// this is your iterator
function () {   
    if(currentImage >= imageCount)
        return null;
    return "http://my/server/GetImage?" + (currentImage++);
},

// this is your action - coincidentally, no adaptor for the correct signature is necessary
LoadImageAsync,

// these are your outs
function () { console.log("All OK."); },
function () { console.log("FAILED!"); }
);

2

Neden bu şekilde yaptığınızı çözmem için biraz zaman aldım, ama bence bu istediğiniz şeye yakın olabilir mi?

function loadImages() {
   var countRemainingToLoad = 0;
   var failures = 0;

   myRepository.each(function (myData) {
      countRemainingToLoad++;

      LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) {
            if (success) {
                myData.Image = data;
            } else {
                write("error loading image " + myData.Id);
                failures++;
            }
            countRemainingToLoad--;
            if (countRemainingToLoad == 0 && failures == 0) {
                enableStartButton();
            }
        }
    );
}

disableStartButton();
loadImages();

Önce aynı anda yapabildiği kadar çok AJAX isteğini kapatır ve Başlat düğmesini etkinleştirmeden önce hepsi tamamlanana kadar bekler. Bu sıralı bir bekleyişten daha hızlı olacak ve bence takip etmesi çok daha kolay.

EDIT : Bu kullanılabilir olduğunu varsayar .each()ve bu myRepositorybir dizi olduğunu unutmayın. Kullanılamıyorsa, bunun yerine burada kullandığınız döngü yinelemesine dikkat edin - bu, geri arama için kapatma özelliklerinden yararlanır. LoadImageAsyncÖzel bir kütüphanenin bir parçası gibi göründüğü için , elinizde neler olduğundan emin değilim - Google'da sonuç görmüyorum.


+1, elimde .each()mevcut ve şimdi bahsettiğinize göre, yükü sırayla yapmak kesinlikle gerekli değil. Kesinlikle çözümünüzü deneyeceğim. (Her ne kadar vski'nin cevabını kabul edeyim, çünkü orijinal, daha genel bir soruya daha yakın.)
Heinzi

@Heinzi Ne kadar farklı olduğu konusunda hemfikir, ancak (bence) bu aynı zamanda farklı dillerin aynı şeyi ele almak için nasıl farklı yollara sahip olduğuna dair iyi bir örnek. Bir şey farklı bir dile tercüme edilirken garip geliyorsa, muhtemelen farklı bir paradigma kullanarak bunu yapmanın daha kolay bir yolu vardır.
12'de Izkata

1

Feragatname: Bu cevap özellikle sorununuzu cevaplamaz, şu soruya verilen genel bir cevaptır: "Eşzamansız kod ve geri aramalar kullanmamıza rağmen kodumu okunabilir tutmak için izleyebileceğim iyi kurulmuş en iyi uygulama kalıpları var mı?"

Bildiğim kadarıyla, bununla başa çıkacak "köklü" bir model yoktur. Ancak, iç içe geri arama kabuslarını önlemek için kullanılan iki tür yöntem gördüm.

1 / Anonim geri aramalar yerine adlandırılmış işlevleri kullanma

    function start() {
        mongo.findById( id, handleDatas );
    }

    function handleDatas( datas ) {
        // Handle the datas returned.
    }

Bu şekilde, anonim işlevin mantığını başka bir işlevde göndererek yuvalamayı önlersiniz.

2 / Akış yönetimi kitaplığı kullanma. Ben kullanmak ister Adım ama sadece bir tercih meselesi. Bu arada LinkedIn'in kullandığı.

    Step( {
        function start() {
            // the "this" magically sends to the next function.
            mongo.findById( this );
        },

        function handleDatas( el ) {
            // Handle the datas.
            // Another way to use it is by returning a value,
            // the value will be sent to the next function.
            // However, this is specific to Step, so look at
            // the documentation of the library you choose.
            return value;
        },

        function nextFunction( value ) {
            // Use the returned value from the preceding function
        }
    } );

Çok fazla iç içe geri arama kullandığımda bir akış yönetimi kitaplığı kullanıyorum, çünkü bunu kullanan bir sürü kod olduğunda çok daha okunabilir.


0

Basitçe söylemek gerekirse, JavaScript'in sözdizimsel şekeri yoktur await.
Ancak "son" kısmı işlevin altına taşımak kolaydır; ve anonim bir işlev yürüten bir işlevle, ona bir başvuru bildirmekten kaçınabiliriz.

disableStartButton();

(function(i, count) {
    var loadImage = arguments.callee;
    myData = myRepository[i];

    LoadImageAsync("http://my/server/GetImage?" + myData.Id,
        function(success, data) { 
            if (!success) {
                write("error loading image " + myData.Id);

            } else {
                myData.Image = data;
                if (i < count) {
                    loadImage(i + 1, count);

                } else {
                    write("success");
                    enableStartButton();
                    return;

                }

            }

        }
    );
})(0, myRepository.length);

Ayrıca "son" kısmını işleve başarılı bir geri arama olarak da iletebilirsiniz.

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.