Geri aramalar ve Sözler arasında gerçekten temel bir fark var mı?


94

Tek iş parçacıklı asenkron programlama yaparken, aşina olduğum iki ana teknik var. En yaygın olanı geri aramaları kullanmaktır. Bu, geri çağırma işlevini parametre olarak asenkronize eden işleve geçmek anlamına gelir. Eşzamansız işlem tamamlandığında geri arama çağrılır.

jQueryBu şekilde tasarlanan bazı tipik kodlar:

$.get('userDetails', {'name': 'joe'}, function(data) {
    $('#userAge').text(data.age);
});

Bununla birlikte, önceki kod bittiğinde, birbiri ardına gelen ek zaman uyumsuz çağrılar yapmak istediğimizde bu kod türü dağınık ve iç içe geçmiş olabilir.

Yani ikinci bir yaklaşım Promises kullanıyor. Bir Söz, henüz bulunmayan bir değeri temsil eden bir nesnedir. Değer okumaya hazır olduğunda çağrılacak olan geri çağrıları ayarlayabilirsiniz.

Sözler ve geleneksel geri aramalar yaklaşımı arasındaki fark, zaman uyumsuz yöntemlerin, istemcinin geri aramayı başlattığı sözde nesneleri eşzamanlı olarak döndürmesidir. Örneğin, AngularJS’deki Promises’i kullanan benzer kod:

$http.get('userDetails', {'name': 'joe'})
    .then(function(response) {
        $('#userAge').text(response.age);
    });

Öyleyse benim sorum şu: gerçek bir fark var mı? Fark tamamen sözdizimsel görünüyor.

Bir tekniği diğerine kullanmak için daha derin bir sebep var mı?


8
Evet: geri aramalar sadece birinci sınıf fonksiyonlardır. Sözler, operasyonları değerler üzerinde zincirlemek için bir araya getirilebilen bir mekanizma sağlayan ve uygun bir arayüz sağlamak için geri çağırma işlevleriyle daha üst düzey işlevler kullanan monadlardır.
amon


5
@gnat: İki soru / cevabın göreceli kalitesi göz önüne alındığında, yinelenen oy IMHO etrafında başka bir yol olmalıdır.
Bart van Ingen Schenau

Yanıtlar:


110

Vaatlerin sadece sözdizimsel şeker olduğunu söylemek doğru olur. Geri aramalarla yapabileceğiniz her şeyi geri aramalarla yapabileceğinize söz veriyor. Aslında, en umut verici uygulamalar istediğiniz zaman ikisi arasında dönüşüm yolları sunar.

Sözlerin genellikle daha iyi olmasının en derin nedeni, daha iyi yapılabilmeleridir, bu da kabaca birden fazla sözün birleştirilmesinin "sadece işe yaraması" anlamına gelirken, aynı zamanda çok sayıda geri çağrının birleştirilmesi sık sık yapılmaz. Örneğin, bir değişkene bir söz vermek ve daha sonra buna ek işleyiciler eklemek ve hatta yalnızca tüm sözler çözüldükten sonra yürütülen geniş bir söz grubuna bir işleyici eklemek bile önemsizdir. Bunları geri aramalarla taklit edebilmenize rağmen, çok daha fazla kod alır, doğru şekilde yapılması çok zordur ve sonuç genellikle daha az bakım gerektirebilir.

En büyük (ve en ince) yollardan biri, sözde besteciliğini kazanma vaadi yollarından biri, getiri değerlerinin ve yakalanmamış istisnaların tek tip ele alınmasıdır. Geri aramalarla, bir istisnanın nasıl ele alındığı, tamamen iç içe geçmiş birçok geri aramadan hangisinin onu attığına ve geri aramaları alan işlevlerden hangisinin uygulanmasında bir dene / yetişme olduğuna bağlı olabilir. Vaatler ile, biliyorum bir geri çağırma işlevi kaçar bir durum yakalandı ve birlikte verilen hata işleyicisi geçirilir olacağını .error()ya .catch().

Tek bir geri arama ile tek bir söz yerine verdiğiniz örnek için, önemli bir fark olmadığı doğrudur. Bir zilyon geri çağrısına sahip olduğunuzda, bir zilyon karşı sözlere dayalı kodun daha güzel görünme eğiliminde olduğuna dair söz verir.


İşte size sözlerle yazılmış bir varsayımsal kod ve daha sonra neden bahsettiğim hakkında bir fikir verecek kadar karmaşık olması gereken geri aramalarla yapılan bir girişim.

Sözlerle:

createViewFilePage(fileDescriptor) {
    getCurrentUser().then(function(user) {
        return isUserAuthorizedFor(user.id, VIEW_RESOURCE, fileDescriptor.id);
    }).then(function(isAuthorized) {
        if(!isAuthorized) {
            throw new Error('User not authorized to view this resource.'); // gets handled by the catch() at the end
        }
        return Promise.all([
            loadUserFile(fileDescriptor.id),
            getFileDownloadCount(fileDescriptor.id),
            getCommentsOnFile(fileDescriptor.id),
        ]);
    }).then(function(fileData) {
        var fileContents = fileData[0];
        var fileDownloads = fileData[1];
        var fileComments = fileData[2];
        fileTextAreaWidget.text = fileContents.toString();
        commentsTextAreaWidget.text = fileComments.map(function(c) { return c.toString(); }).join('\n');
        downloadCounter.value = fileDownloads;
        if(fileDownloads > 100 || fileComments.length > 10) {
            hotnessIndicator.visible = true;
        }
    }).catch(showAndLogErrorMessage);
}

Geri aramalarla:

createViewFilePage(fileDescriptor) {
    setupWidgets(fileContents, fileDownloads, fileComments) {
        fileTextAreaWidget.text = fileContents.toString();
        commentsTextAreaWidget.text = fileComments.map(function(c) { return c.toString(); }).join('\n');
        downloadCounter.value = fileDownloads;
        if(fileDownloads > 100 || fileComments.length > 10) {
            hotnessIndicator.visible = true;
        }
    }

    getCurrentUser(function(error, user) {
        if(error) { showAndLogErrorMessage(error); return; }
        isUserAuthorizedFor(user.id, VIEW_RESOURCE, fileDescriptor.id, function(error, isAuthorized) {
            if(error) { showAndLogErrorMessage(error); return; }
            if(!isAuthorized) {
                throw new Error('User not authorized to view this resource.'); // gets silently ignored, maybe?
            }

            var fileContents, fileDownloads, fileComments;
            loadUserFile(fileDescriptor.id, function(error, result) {
                if(error) { showAndLogErrorMessage(error); return; }
                fileContents = result;
                if(!!fileContents && !!fileDownloads && !!fileComments) {
                    setupWidgets(fileContents, fileDownloads, fileComments);
                }
            });
            getFileDownloadCount(fileDescriptor.id, function(error, result) {
                if(error) { showAndLogErrorMessage(error); return; }
                fileDownloads = result;
                if(!!fileContents && !!fileDownloads && !!fileComments) {
                    setupWidgets(fileContents, fileDownloads, fileComments);
                }
            });
            getCommentsOnFile(fileDescriptor.id, function(error, result) {
                if(error) { showAndLogErrorMessage(error); return; }
                fileComments = result;
                if(!!fileContents && !!fileDownloads && !!fileComments) {
                    setupWidgets(fileContents, fileDownloads, fileComments);
                }
            });
        });
    });
}

Geri bildirimler sürümünde kod yinelemesini azaltmanın bazı sözleri olmasa bile, bazı akıllıca yollar olabilir;


1
Vaatlerin bir diğer büyük avantajı, async / bekliyor ya da yielded vaatler için vaat edilen değerleri geri alan bir koroutin ile "şekerleştirme" ye uygun olmalarıdır . Buradaki avantaj, kaç tane eşzamansız işlem yaptıklarına bağlı olarak değişebilen doğal kontrol akış yapılarında karışma yeteneğine sahip olmanızdır. Bunu gösteren bir sürüm ekleyeceğim.
acjay

9
Geri aramalar ve vaatler arasındaki temel fark, kontrolün tersine çevrilmesidir. Geri aramalarla , API'niz bir geri arama kabul etmelidir , ancak Sözler ile API'niz bir söz vermelidir . Bu birincil farktır ve API tasarımı için geniş etkileri vardır.
cwharris

@ChristopherHarris katılıyorum emin değilim. then(callback)Bir geri aramayı kabul eden Promise üzerinde bir yönteme sahip olmak (bu geri aramayı kabul eden API yerine bir yöntem yerine) IoC ile bir şey yapmak zorunda değildir. Söz, kompozisyon, zincirleme ve hata yönetimi (etkin demiryolu yönelimli programlama) için yararlı olan bir yönlendirme seviyesi getirmektedir, ancak geri arama hala müşteri tarafından yapılmamaktadır, bu nedenle gerçekten IoC bulunmamakta.
dragan.stepanovic

1
@ dragan.stepanovic Haklısınız ve yanlış terminolojiyi kullandım. Aradaki fark dolaylı. Bir geri çağırma ile, sonuçta yapılması gerekenleri zaten bilmelisiniz. Bir söz ile daha sonra karar verebilirsiniz.
cwharris
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.