Sözler sadece geri aramalar değil mi?


430

Birkaç yıldır JavaScript geliştiriyorum ve vaatlerle ilgili karışıklığı anlamıyorum.

Görünüşe göre tek yaptığım değişiklik:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

Hangi zaten async gibi bir kütüphane, gibi bir şey ile kullanabilirsiniz:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

Hangi daha fazla kod ve daha az okunabilir. Burada hiçbir şey kazanmadım, birdenbire sihirli bir şekilde 'düz' değil. Bazı şeyleri vaatlere dönüştürmekten bahsetmiyorum bile.

Peki, buradaki vaatlerle ilgili büyük yaygara nedir?


11
On-topic : Html5Rocks'ta Promises hakkında gerçekten bilgilendirici bir makale var: html5rocks.com/en/tutorials/es6/promises
ComFreek

2
Kabul ettiğiniz cevap, vaatlerin noktası olmayan ve beni sözleri kullanmaya ikna etmeyen önemsiz faydaların aynı eski listesidir:. Beni sözleri kullanmaya ikna eden şey, Oscar'ın cevabında açıklandığı gibi DSL yönü oldu
Esailija

@Esailija iyi, leet konuşmanız beni ikna etti. Bergi'nin birisinin de gerçekten iyi (ve farklı) puanlar verdiğini düşünmeme rağmen diğer yanıtı kabul ettim.
Benjamin Gruenbaum

@Esailija "Beni Oscar'ın cevabı" << "DSL" nedir? ve bahsettiğiniz "DSL yönü" nedir?
monsto

1
@monsto: DSL: Bir sistemin belirli bir alt kümesinde (örn. veritabanıyla konuşmak için SQL veya ORM, kalıpları bulmak için normal ifade vb.) Özel olarak tasarlanmış bir dildir. Bu bağlamda "DSL", kodunuzu Oscar'ın yaptığı gibi yapılandırırsanız, neredeyse zaman uyumsuz işlemlerin belirli bağlamını ele almak için JavaScript'i destekleyen sözdizimsel şeker gibi olan Promise'ın API'sıdır. Vaatler, onları programcının bu tür yapıların biraz zor zihinsel akışını daha kolay kavramasını sağlamak için tasarlanmış bir dile dönüştüren bazı deyimler yaratır.
Michael Ekoka

Yanıtlar:


631

Sözler geri arama değildir. Bir söz, eşzamansız bir işlemin gelecekteki sonucunu temsil eder . Tabii ki, onları istediğiniz gibi yazmak, çok az fayda elde edersiniz. Ancak bunları kullanılacakları şekilde yazarsanız, eşzamansız kodu, eşzamanlı koda benzeyecek ve takip edilmesi çok daha kolay bir şekilde yazabilirsiniz:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

Kesinlikle, çok daha az kod değil, ama çok daha okunabilir.

Ama bu son değil. Gerçek faydaları keşfedelim: Adımlardan herhangi birinde herhangi bir hata olup olmadığını kontrol etmek isterseniz ne olur? Geri aramalarla yapmak cehennem olurdu, ancak vaatlerle bir parça kek:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
});

Bir try { ... } catchblokla hemen hemen aynı .

Daha iyi:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
}).then(function() {
     //do something whether there was an error or not
     //like hiding an spinner if you were performing an AJAX request.
});

Ve daha da iyi: o 3 aramalar için ne api, api2, api3eş zamanlı olarak çalıştırabilir (örneğin onlar AJAX çağrıları olsaydı) ama üç beklemek gerekiyordu? Vaatler olmadan, bir çeşit sayaç oluşturmanız gerekir. ES6 gösterimini kullanmak, başka bir çocuk oyuncağı ve oldukça düzgün:

Promise.all([api(), api2(), api3()]).then(function(result) {
    //do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
    //handle the error. At least one of the promises rejected.
});

Umutlar şimdi yeni bir ışıkta görüyorsun.


124
Bunu gerçekten "Promise" olarak adlandırmamalıydılar. "Gelecek" en az 100 kat daha iyi.
Pacerier

12
@Pacerier Gelecek jQuery tarafından lekelenmediği için mi?
Esailija

5
Alternatif desen (ne istendiğine bağlı olarak: api (). Sonra (api2). Sonra (api3). Sonra (doWork); Yani, api2 / api3 işlevleri son adımdan girdi alır ve yeni vaatler verirse, fazladan
sarılmadan

1
Ne var eğer işlemci kullanımı zaman uyumsuz olduğu api2ve api3? sonuncusu .thensadece bu asenkron işlemler tamamlandıktan sonra çağrılır mı?
NiCk Newman

8
Beni neden etiketledin? Dilbilgisini biraz düzelttim. Ben JS uzmanı değilim. :)
Scott Arciszewski

169

Evet, Vaatler zaman uyumsuz geri çağrılardır. Geri aramaların yapamayacağı hiçbir şey yapamazlar ve düz geri aramalarda olduğu gibi eşzamansızlıkla aynı sorunlarla karşılaşırsınız.

Ancak, Vaatler geri aramalardan daha fazlasıdır. Onlar çok güçlü bir soyutlama, daha az hata eğilimli kazan plakası ile daha temiz ve daha iyi, fonksiyonel kod sağlar.

Peki ana fikir nedir?

Vaatler, tek (asenkron) hesaplamanın sonucunu temsil eden nesnelerdir. Bu sonuca sadece bir kez karar verirler . Bunun ne anlama geldiği birkaç şey var:

Bir gözlemci modeli uygulamak vaat ediyor:

  • Görev tamamlanmadan önce değeri kullanacak geri çağrıları bilmenize gerek yoktur.
  • Geri aramalarınızı işlevlerinize bağımsız değişken olarak beklemek yerine, kolayca returnbir Promise nesnesi
  • Söz değeri saklar ve istediğiniz zaman şeffaf bir geri arama ekleyebilirsiniz. Sonuç mevcut olduğunda çağrılacaktır. "Şeffaflık", bir söz verdiğinizde ve ona bir geri arama eklediğinizde, sonucun henüz gelip gelmediği kodunuzda bir fark yaratmadığını gösterir - API ve sözleşmeler aynıdır, önbelleğe alma / notlamayı çok basitleştirir.
  • Kolayca birden fazla geri arama ekleyebilirsiniz

Vaatler chainable olan ( monadic , isterseniz ):

  • Eğer bir söz temsil ettiğini değerini dönüştürmek için gerekiyorsa, harita bir söz üzerinde işlev dönüşümü ve dönüştürülmüş sonucu temsil eden yeni bir söz geri almak. Bir şekilde kullanmak için değeri eşzamanlı olarak alamazsınız, ancak söz verilen bağlamda dönüşümü kolayca kaldırabilirsiniz . Ortak plaka geri araması yok.
  • İki eşzamansız görevi zincirlemek istiyorsanız, .then()yöntemi kullanabilirsiniz . İlk sonuçla birlikte çağrılacak bir geri çağırma gerekir ve geri çağrının geri döneceği vaadinin sonucu için bir söz verir.

Kulağa karmaşık mı geliyor? Kod örneği zamanı.

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)

Düzleştirme sihirli bir şekilde gelmez, ancak kolayca yapabilirsiniz. Çok iç içe geçmiş örneğiniz için, (yakın) eşdeğeri

api1().then(api2).then(api3).then(/* do-work-callback */);

Bu yöntemlerin kodunu görmek anlamaya yardımcı oluyorsa, birkaç satırda en temel vaat lib .

Vaatlerle ilgili büyük yaygara nedir?

Promise soyutlaması, işlevlerin çok daha iyi birleştirilebilirliğine izin verir. Örneğin, thenzincirlemenin yanında , allişlev çoklu paralel bekleme vaatlerinin birleşik sonucu için bir vaat oluşturur.

Son fakat en az değil, Sözler entegre hata işleme ile birlikte gelir. Hesaplamanın sonucu, ya vaadin bir değerle yerine getirilmesi ya da bir sebeple reddedilmesi olabilir . Tüm kompozisyon işlevleri bunu otomatik olarak ele alır ve vaat zincirlerindeki hataları yayar, böylece düz geri çağrı uygulamasının aksine, her yerde açıkça ilgilenmeniz gerekmez. Sonunda, meydana gelen tüm istisnalar için özel bir hata geri çağrısı ekleyebilirsiniz.

Bazı şeyleri vaatlere dönüştürmekten bahsetmiyorum bile.

Bu aslında iyi vaat kütüphaneleri ile oldukça önemsiz, bkz. Mevcut bir geri arama API'sini vaatlere nasıl dönüştürebilirim?


merhaba Bergi, bu SO sorusuna eklemek için ilginç bir şey var mı? stackoverflow.com/questions/22724883/…
Sebastien Lorber

1
@Sebastien: Scala (henüz) hakkında fazla bir şey bilmiyorum ve sadece Benjamin'in söylediklerini tekrarlayabilirim :-)
Bergi

3
Sadece küçük bir açıklama: .then(console.log)console.log konsol içeriğine bağlı olduğundan kullanamazsınız . Bu şekilde geçersiz bir çağırma hatasına neden olur. Bağlam bağlamak için console.log.bind(console)veya tuşunu kullanın x => console.log(x).
Tamas Hegedus

3
@hege_hegedus: consoleYöntemlerin zaten bağlı olduğu ortamlar var . Ve elbette, sadece her iki yuvanın da tamamen aynı davranışa sahip olduğunu söyledim, bunların hiçbirinin işe yaramayacağını değil :-P
Bergi

1
Bu harikaydı. Bu ihtiyacım olan şey: daha az kod ve daha fazla yorum. Teşekkür ederim.
Adam Patterson

21

ES6 ok fonksiyonları ile önceden belirlenmiş cevaplara ek olarak Promises, mütevazi bir şekilde parlayan küçük mavi cüceden doğrudan kırmızı bir deve dönüşür. Bu bir süpernovaya çökmek üzeredir:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

Oligofren'in işaret ettiği gibi , api çağrıları arasında tartışma olmadan anonim sarıcı işlevlerine ihtiyacınız yoktur:

api().then(api2).then(api3).then(r3 => console.log(r3))

Ve son olarak, süper kütleli bir kara delik seviyesine ulaşmak istiyorsanız, Vaatler beklenebilir:

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}

9
"ES6 ok fonksiyonları ile Vaatler, mütevazi bir şekilde parlayan küçük bir mavi yıldızdan kırmızı bir deve dönüşüyor. Bu bir süpernovaya çökmek üzeredir" Çeviri: ES6 ok işlevlerini Promises ile birleştirmek harika :)
user3344977

3
Bu, Vaatlerin kasıtlı olduğunu düşünmediğim kozmik bir felaket gibi geliyor.
Michael McGinnis

Eğer argümanları kullanmak değilseniz apiXyöntemlerle, siz de tamamen ok fonksiyonları atlamak olabilir: api().then(api2).then(api3).then(r3 => console.log(r3)).
oligofren

@MichaelMcGinnis - Promises'ın donuk bir geri arama cehennemi üzerindeki olumlu etkisi, uzayın karanlık bir köşesinde patlayan bir süpernova gibidir.
John Weisz

Şiirsel olarak kastettiğini biliyorum, ama sözler "süpernova" dan oldukça uzak. Monadik yasayı çiğnemek veya iptal etme veya birden fazla değer döndürme gibi daha güçlü kullanım durumları için destek eksikliği akla geliyor.
Dmitri Zaitsev

15

Yukarıdaki müthiş cevaplara ek olarak, 2 puan daha eklenebilir:

1. Anlamsal fark:

Vaatler yaratıldıktan sonra çözülmüş olabilir. Bu, olaylardan ziyade koşulları garanti ettikleri anlamına gelir . Zaten çözülmüşlerse, ona iletilen çözülmüş işlev hala çağrılır.

Tersine, geri aramalar olayları işler. Bu nedenle, ilgilendiğiniz olay geri arama kaydedilmeden önce gerçekleşmişse geri arama yapılmaz.

2. Kontrolün ters çevrilmesi

Geri aramalar kontrolün ters çevrilmesini içerir. Herhangi bir API ile geri çağrı işlevi kaydettiğinizde, Javascript çalışma zamanı geri çağrı işlevini saklar ve çalıştırılmaya hazır olduğunda olay döngüsünden çağırır.

Açıklama için Javascript Olay döngüsüne bakın .

İle Promises , kontrol çağıran program ile yaşamaktadır. Eğer vaat nesnesini saklarsak .then () yöntemi herhangi bir zamanda çağrılabilir .


1
Nedenini bilmiyorum ama bu daha iyi bir yanıt gibi görünüyor.
radiantshaw

13

Diğer cevaplara ek olarak, ES2015 sözdizimi vaatlerle sorunsuz bir şekilde birleşerek daha da kaynatma plakası kodunu azaltır:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});

5

Vaatler geri çağırma değildir, her ikisi de zaman uyumsuz programlamayı kolaylaştıran programlama deyimleridir. Vaatleri geri döndüren koroutinleri veya jeneratörleri kullanarak zaman uyumsuz / beklemede bir programlama kullanmak, böyle bir deyim olarak düşünülebilir. Bu deyimlerin farklı programlama dillerinde (Javascript dahil) karşılaştırılması burada: https://github.com/KjellSchubert/promise-future-task


5

Hayır, hiç de değil.

Geri aramalar , başka bir işlevin yürütülmesi tamamlandıktan sonra çağrılacak ve sonra çalıştırılacak olan JavaScript'teki İşlevlerdir . Peki nasıl oluyor?

Aslında, JavaScript'te işlevlerin kendisi nesne olarak kabul edilir ve bu nedenle diğer tüm nesneler gibi, işlevler bile diğer işlevlere argüman olarak gönderilebilir . Birinin düşünebileceği en yaygın ve genel kullanım durumu, JavaScript'teki setTimeout () işlevidir.

Vaatler , geri çağrılarla aynı şeyi yapmaya kıyasla, zaman uyumsuz kodları işlemek ve yapılandırmak için çok daha doğaçlama bir yaklaşımdan başka bir şey değildir.

Promise yapıcı işlevinde iki Geri Arama alır: çöz ve reddet. Vaatlerin içindeki bu geri aramalar, hata yönetimi ve başarı vakaları üzerinde ayrıntılı bir kontrol sağlıyor. Çözüm geri çağrısı, başarılı bir şekilde verilen sözün yürütülmesi ve hata durumlarının üstesinden gelmek için geri çağırma reddetme işlevi kullanıldığında kullanılır.


2

Hiçbir söz sadece geri aramalar sarıcı değildir

Örnek js düğümü ile javascript yerel vaatlerini kullanabilirsiniz

my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
    request.get(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        resolve(body);
    }
    else {
        reject(error);
    }
    })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
    console.log(e);
})
.then(function (result) {
    res.end(result);
}
)

})


var server = app.listen(8081, function () {

var host = server.address().address
var port = server.address().port

console.log("Example app listening at http://%s:%s", host, port)

})


//run webservice on browser : http://localhost:8081/listAlbums

1

JavaScript Promises, bir Promise çözümlendikten veya reddedildikten sonra ne yapılacağını belirlemek için geri arama işlevlerini kullanır, bu nedenle her ikisi de temelde farklı değildir. Promises'ın arkasındaki ana fikir, geri aramalar almaktır - özellikle bir tür eylem gerçekleştirmek istediğiniz iç içe geri aramalar, ancak daha okunabilir olacaktır.


0

Vaatlere genel bakış:

JS'de vaatlerde eşzamansız işlemleri (örneğin veritabanı çağrıları, AJAX çağrıları) sarabiliriz. Genellikle alınan veriler üzerinde ek bir mantık çalıştırmak istiyoruz. JS vaatleri, eşzamansız işlemlerin sonucunu işleyen işleyici işlevlerine sahiptir. İşleyici işlevlerinde, önceki eşzamansız işlemlerin değerine dayanan başka eşzamansız işlemler de olabilir.

Bir vaat her zaman aşağıdaki 3 durumdan oluşur:

  1. beklemede: ne yerine getirilmiş ne de reddedilmiş her vaatin başlangıç ​​durumu.
  2. yerine getirildi: İşlem başarıyla tamamlandı.
  3. reddedildi: İşlem başarısız oldu.

Bekleyen bir vaat, bir değerle çözülebilir / yerine getirilebilir veya reddedilebilir. Ardından, bağımsız değişkenler olarak geri çağrıları alan aşağıdaki işleyici yöntemleri çağrılır:

  1. Promise.prototype.then() : Vaat çözüldüğünde, bu işlevin geri çağırma bağımsız değişkeni çağrılır.
  2. Promise.prototype.catch() : Vaat reddedildiğinde bu işlevin geri çağırma bağımsız değişkeni çağrılır.

Her ne kadar yukarıdaki yöntemler beceri geri çağrı argümanları alsa da, burada sadece geri çağrıları kullanmaktan çok daha üstündürler, çok açıklığa kavuşacak bir örnektir:

Misal

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}

createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • CreateProm işlevi, 1 saniye sonra rastgele bir Nr'ye göre çözülen veya reddedilen bir vaatler oluşturur
  • Söz çözümlenirse, ilk thenyöntem çağrılır ve çözümlenen değer geri çağırma argümanı olarak iletilir
  • Söz reddedilirse, ilk catchyöntem çağrılır ve reddedilen değer bağımsız değişken olarak aktarılır
  • catchVe thenyöntemler biz zincirleyin neden olduğunu vaat dönün. Herhangi bir döndürülen değeri Promise.resolveve içine atılan herhangi bir değeri ( throwanahtar kelimeyi kullanarak ) sararlar Promise.reject. Dolayısıyla, döndürülen herhangi bir değer bir söze dönüştürülür ve bu söz üzerinde tekrar bir işleyici işlevi çağırabiliriz.
  • Promise zincirleri, iç içe geri aramalardan daha ince ayarlanmış kontrol ve daha iyi genel bakış sağlar. Örneğin catchyöntem, catchişleyiciden önce oluşan tüm hataları işler .
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.