"Geri arama cehennemi" nedir ve RX bunu nasıl ve neden çözer?


113

JavaScript ve node.js bilmeyen biri için "callback cehennemi" nin ne olduğunu açıklayan basit bir örnekle birlikte net bir tanım verebilir mi?

"Geri arama cehennemi sorunu" ne zaman (ne tür ayarlarda) ortaya çıkar?

Neden oluşur?

"Geri arama cehennemi" her zaman eşzamansız hesaplamalarla mı ilgilidir?

Veya "geri arama cehennemi" tek bir iş parçacıklı uygulamada da meydana gelebilir mi?

Reaktif Kursu Coursera'da aldım ve Erik Meijer derslerinden birinde RX'in "geri arama cehennemi" sorununu çözdüğünü söyledi. Coursera forumunda "geri arama cehennemi" nin ne olduğunu sordum ama net bir cevabım yok.

Basit bir örnekle "geri arama cehennemi" ni açıkladıktan sonra, RX'in bu basit örnekte "geri arama cehennemi sorununu" nasıl çözdüğünü de gösterebilir misiniz?

Yanıtlar:


136

1) JavaScript ve node.js bilmeyen biri için "geri arama cehennemi" nedir?

Bu diğer soruda bazı Javascript geri çağırma cehennemi örnekleri var: Node.js'de zaman uyumsuz işlevlerin uzun süre yuvalanmasından nasıl kaçınılır

Javascript'teki sorun, bir hesaplamayı "dondurmanın" ve "geri kalanını" ikincisini (eşzamansız olarak) yürütmenin tek yolunun, "geri kalanını" bir geri aramanın içine koymak olmasıdır.

Örneğin, şöyle görünen bir kod çalıştırmak istediğimi varsayalım:

x = getData();
y = getMoreData(x);
z = getMoreData(y);
...

Şimdi getData işlevlerini eşzamansız yapmak istersem, yani değerlerini döndürmelerini beklerken başka bir kod çalıştırma şansım olursa ne olur? Javascript'te, tek yol, bir eşzamansız hesaplamaya dokunan her şeyi devam ettirme geçiş stilini kullanarak yeniden yazmaktır. :

getData(function(x){
    getMoreData(x, function(y){
        getMoreData(y, function(z){ 
            ...
        });
    });
});

Bu sürümün öncekinden daha çirkin olduğuna kimseyi ikna etmem gerektiğini sanmıyorum. :-)

2) "Geri arama cehennemi sorunu" ne zaman (ne tür ayarlarda) ortaya çıkar?

Kodunuzda çok sayıda geri arama işlevi olduğunda! Kodunuzda bunlardan daha fazlasına sahip olduğunuzda onlarla çalışmak zorlaşır ve döngüler, deneme blokları ve benzeri şeyler yapmanız gerektiğinde özellikle kötüleşir.

Örneğin, bildiğim kadarıyla, JavaScript'te, önceki dönüşlerden sonra birinin çalıştırıldığı bir dizi eşzamansız işlevi yürütmenin tek yolu, özyinelemeli bir işlev kullanmaktır. For döngüsü kullanamazsınız.

// we would like to write the following
for(var i=0; i<10; i++){
    doSomething(i);
}
blah();

Bunun yerine şunu yazmamız gerekebilir:

function loop(i, onDone){
    if(i >= 10){
        onDone()
    }else{
        doSomething(i, function(){
            loop(i+1, onDone);
        });
     }
}
loop(0, function(){
    blah();
});

//ugh!

StackOverflow'da bu tür bir şeyin nasıl yapılacağını soran soruların sayısı, bunun ne kadar kafa karıştırıcı olduğunun bir kanıtıdır :)

3) Neden oluşur?

Bunun nedeni, JavaScript'te bir hesaplamayı geciktirmenin tek yolunun, eşzamansız çağrı dönüşlerinden sonra çalışmasının gecikmiş kodu bir geri çağrı işlevinin içine koymak olmasıdır. Geleneksel eşzamanlı tarzda yazılmış kodu geciktiremezsiniz, böylece her yerde iç içe geçmiş geri aramalarla sonuçlanırsınız.

4) Veya "geri arama cehennemi" tek bir iş parçacıklı uygulamada da meydana gelebilir mi?

Eşzamansız programlama eşzamanlılıkla ilgiliyken, tek iş parçacığı paralellik ile ilgilidir. İki kavram aslında aynı şey değil.

Yine de tek bir iş parçacığı bağlamında eşzamanlı koda sahip olabilirsiniz. Aslında, geri arama cehenneminin kraliçesi olan JavaScript, tek iş parçacıklıdır.

Eşzamanlılık ve paralellik arasındaki fark nedir?

5) RX'in "geri arama cehennemi problemini" nasıl çözdüğünü bu basit örnekte gösterebilir misiniz?

Özellikle RX hakkında hiçbir şey bilmiyorum, ancak genellikle bu sorun, programlama dilinde zaman uyumsuz hesaplama için yerel destek eklenerek çözülür. Uygulamalar değişebilir ve şunları içerebilir: zaman uyumsuz, üreteçler, eş anlamlılar ve callcc.

Python'da, önceki döngü örneğini aşağıdaki satırlar boyunca bir şeyle uygulayabiliriz:

def myLoop():
    for i in range(10):
        doSomething(i)
        yield

myGen = myLoop()

Bu tam kod değildir, ancak fikir şu ki "verim", birisi myGen.next () 'yi çağırana kadar for döngümüzü duraklatır. Önemli olan, o yinelemeli loopişlevde yapmak zorunda olduğumuz gibi, mantığı "içten dışa" çevirmeye gerek kalmadan bir for döngüsü kullanarak kodu yazabilmemizdir .


Yani geri arama cehennemi yalnızca eşzamansız bir ortamda olabilir? Kodum tamamen eşzamanlıysa (yani eşzamanlılık yoksa) cevabınızı doğru anlarsam "geri arama cehennemi" gerçekleşemez, bu doğru mu?
jhegedus

Callback cehenneminin, süreklilik geçiş stilini kullanarak kodlamanın ne kadar sinir bozucu olduğuyla daha çok ilgisi var. Teorik olarak, normal bir program için bile tüm işlevlerinizi CPS stilini kullanarak yeniden yazabilirsiniz (wikipedia makalesinde bazı örnekler vardır), ancak, iyi bir nedenden ötürü, çoğu insan bunu yapmaz. Javascript zaman uyumsuz programlamada olduğu gibi, genellikle sadece mecbur kalırsak devamlı geçiş stilini kullanırız.
hugomg

btw, reaktif uzantılar için Google'da arama yaptım ve bunların bir Promise kitaplığına daha çok benzedikleri ve zaman uyumsuz sözdizimini tanıtan bir dil uzantısı olmadığı izlenimini edindim. Sözler, geri arama yuvalama ve istisna işlemeyle başa çıkmaya yardımcı olur, ancak sözdizimi uzantıları kadar düzgün değillerdir. For döngüsü kod için hala can sıkıcıdır ve yine de kodu senkron stilden söz stiline çevirmeniz gerekir.
hugomg

1
RX'in genel olarak nasıl daha iyi bir iş çıkardığını açıklamalıyım. RX bildirim amaçlıdır. Programın daha sonra meydana gelen olaylara nasıl yanıt vereceğini, başka herhangi bir program mantığını etkilemeden açıklayabilirsiniz. Bu, ana döngü kodunu olay işleme kodundan ayırmanıza olanak tanır. Durum değişkenlerini kullanırken kabus olan eşzamansız olay sıralaması gibi ayrıntıları kolayca işleyebilirsiniz. RX'in, 3 ağ yanıtı döndürüldükten sonra yeni bir ağ isteği gerçekleştirmek için en temiz uygulama olduğunu veya biri dönmezse tüm zinciri hatayla işlemek için buldum. Daha sonra kendini sıfırlayabilir ve aynı 3 olayı bekleyebilir.
colintheshots

İlgili bir yorum daha: RX temelde CPS ile ilgili olan devam monadidir, yanılmıyorsam, bu aynı zamanda RX'in geri arama / cehennem sorunu için nasıl / neden iyi olduğunu da açıklayabilir.
jhegedus

30

Sadece soruyu cevaplayın: RX'in bu basit örnekte "geri arama cehennemi problemini" nasıl çözdüğünü de gösterebilir misiniz?

Sihir flatMap. @ Hugomg örneği için aşağıdaki kodu Rx'de yazabiliriz:

def getData() = Observable[X]
getData().flatMap(x -> Observable[Y])
         .flatMap(y -> Observable[Z])
         .map(z -> ...)...

Sanki eşzamanlı FP kodları yazıyor gibisiniz, ama aslında onları eşzamansız hale getirebilirsiniz Scheduler.


26

Rx'in geri arama cehennemini nasıl çözdüğü sorusunu ele almak için :

Önce geri arama cehennemini yeniden tanımlayalım.

Kişi, gezegen ve galaksi olmak üzere üç kaynak elde etmek için http yapmamız gereken bir durum hayal edin. Amacımız kişinin yaşadığı galaksiyi bulmaktır. Önce kişiyi, sonra gezegeni, sonra galaksiyi almalıyız. Bu, üç eşzamansız işlem için üç geri arama demektir.

getPerson(person => { 
   getPlanet(person, (planet) => {
       getGalaxy(planet, (galaxy) => {
           console.log(galaxy);
       });
   });
});

Her geri arama iç içe yerleştirilmiştir. Her iç geri arama, ebeveynine bağlıdır. Bu, geri arama cehenneminin "kıyamet piramidi" tarzına yol açar . Kod bir> işaretine benziyor.

Bunu RxJ'lerde çözmek için şöyle bir şey yapabilirsiniz:

getPerson()
  .map(person => getPlanet(person))
  .map(planet => getGalaxy(planet))
  .mergeAll()
  .subscribe(galaxy => console.log(galaxy));

mergeMapAKA flatMapoperatörü ile bunu daha kısa ve öz yapabilirsiniz:

getPerson()
  .mergeMap(person => getPlanet(person))
  .mergeMap(planet => getGalaxy(planet))
  .subscribe(galaxy => console.log(galaxy));

Gördüğünüz gibi, kod düzleştirilmiştir ve tek bir yöntem çağrıları zinciri içerir. "Kıyamet piramidimiz" yok.

Bu nedenle, geri arama cehenneminden kaçınılır.

Merak ediyorsanız, sözler geri arama cehenneminden kaçınmanın başka bir yoludur, ancak sözler heveslidir , gözlemlenebilirler gibi tembel değildir ve (genel olarak konuşursak) onları bu kadar kolay iptal edemezsiniz.


Bir JS geliştiricisi değilim, ancak bu kolay bir açıklama
Omar Beshary

15

Geri arama cehennemi, eşzamansız kodda işlev geri aramalarının kullanımının belirsizleştiği veya takip edilmesinin zor olduğu herhangi bir koddur. Genel olarak, birden fazla yönlendirme düzeyi olduğunda, geri aramaları kullanan kodun izlenmesi, yeniden düzenlenmesi ve test edilmesi zorlaşabilir. Bir kod kokusu, işlev değişmezlerinin birden çok katmanını geçmesi nedeniyle birden çok girinti düzeyidir.

Bu genellikle davranışın bağımlılıkları olduğunda olur, yani A'nın, B'nin C'den önce olması gerekmeden önce olması gerektiğinde, O zaman şöyle bir kod alırsınız:

a({
    parameter : someParameter,
    callback : function() {
        b({
             parameter : someOtherParameter,
             callback : function({
                 c(yetAnotherParameter)
        })
    }
});

Kodunuzda bunun gibi çok sayıda davranışsal bağımlılığınız varsa, hızla zahmetli hale gelebilir. Özellikle dallanıyorsa ...

a({
    parameter : someParameter,
    callback : function(status) {
        if (status == states.SUCCESS) {
          b(function(status) {
              if (status == states.SUCCESS) {
                 c(function(status){
                     if (status == states.SUCCESS) {
                         // Not an exaggeration. I have seen
                         // code that looks like this regularly.
                     }
                 });
              }
          });
        } elseif (status == states.PENDING {
          ...
        }
    }
});

Bu işe yaramaz. Tüm bu geri aramaları iletmek zorunda kalmadan asenkron kodun belirli bir sırada çalışmasını nasıl sağlayabiliriz?

RX, "reaktif uzantılar" ın kısaltmasıdır. Kullanmadım, ancak Googling, bunun olay tabanlı bir çerçeve olduğunu öne sürüyor, bu da mantıklı. Olaylar, kırılgan eşleşme oluşturmadan kodun sırayla yürütülmesini sağlamak için yaygın bir modeldir . C'nin, yalnızca B'ye "aFinished" yi dinleme olarak adlandırıldıktan sonra gerçekleşen "bFinished" olayını dinletebilirsiniz. Daha sonra kolayca fazladan adımlar ekleyebilir veya bu tür davranışları genişletebilir ve yalnızca test durumunuzda olayları yayınlayarak kodunuzun sırayla çalışıp çalışmadığını kolayca test edebilirsiniz .


1

Geri arama cehennemi, başka bir geri aramanın içinden bir geri aramanın içinde olduğunuz anlamına gelir ve ihtiyaçlarınız karşılanana kadar n'inci aramaya gider.

Set timeout API kullanarak sahte ajax çağrısı örneğini anlayalım, bir tarif API'miz olduğunu varsayalım, tüm tarifi indirmemiz gerekiyor.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Yukarıdaki örnekte 1.5 saniye sonra zamanlayıcı sona erdiğinde içeriden geri arama kodu çalıştırılacak, diğer bir deyişle sahte ajax çağrımız aracılığıyla tüm tarifler sunucudan indirilecektir. Şimdi belirli bir tarif verilerini indirmemiz gerekiyor.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Belirli bir tarif verilerini indirmek için ilk geri aramamızın içine kod yazdık ve tarif kimliğini geçtik.

Şimdi diyelim ki 7638 numaralı tarifin aynı yayıncısının tüm tariflerini indirmemiz gerekiyor.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                    setTimeout(publisher=>{
                        const recipe2 = {title:'Fresh Apple Pie', publisher:'Suru'};
                        console.log(recipe2);
                    }, 1500, recipe.publisher);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Yayıncı adı suru'nun tüm tariflerini indirmek olan ihtiyaçlarımızı tam olarak karşılamak için, ikinci geri aramamızın içine kod yazdık. Geri arama cehennemi denen bir geri arama zinciri yazdığımız açıktır.

Callback cehenneminden kaçınmak istiyorsanız, js es6 özelliği olan Promise'i kullanabilirsiniz, her söz, bir söz dolduğunda çağrılan bir geri arama alır. geri aramanın çözülmesi veya reddedilmesi için iki seçeneği vardır. API çağrısı kararlılığını arayıp aracılığıyla veri geçirebiliriz başarılı varsayalım kararlılığı , kullanarak bu verileri elde edebilirsiniz sonra () . Ancak API'niz başarısız olursa, reddetmeyi kullanabilirsiniz , hatayı yakalamak için catch'i kullanın . Her zaman kullandığınız bir Sözünü unutma sonra kararlılığı ve için yakalama reddetmek için

Bir söz kullanarak önceki geri arama cehennemi problemini çözelim.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        getIds.then(IDs=>{
            console.log(IDs);
        }).catch(error=>{
            console.log(error);
        });
    </script>
</body>

Şimdi belirli tarifi indirin:

<body>
    <script>
        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }
        getIds.then(IDs=>{
            console.log(IDs);
            return getRecipe(IDs[2]);
        }).
        then(recipe =>{
            console.log(recipe);
        })
        .catch(error=>{
            console.log(error);
        });
    </script>
</body>

Şimdi allRecipeOfAPublisher gibi getRecipe gibi başka bir yöntem yazabiliriz ve bu da bir söz verir ve allRecipeOfAPublisher için çözüm sözü almak için başka bir () yazabiliriz, umarım bu noktada bunu kendiniz yapabilirsiniz.

Böylece vaatleri nasıl oluşturacağımızı ve tüketeceğimizi öğrendik, şimdi es8'de tanıtılan async / await kullanarak bir söz vermeyi kolaylaştıralım.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }

        async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

        getRecipesAw();
    </script>
</body>

Yukarıdaki örnekte, arka planda çalışacağı için bir async işlevi kullandık, await anahtar sözcüğünü kullandık her yöntemden önce geri dönen ya da bir vaat çünkü bu vaat yerine getirilene kadar o konumda beklemek, başka bir deyişle getIds çözülene veya red programı tamamlanana kadar aşağıdaki kodlar, ID'ler döndüğünde bu satırın altındaki kodları yürütmeyi durduracak, sonra yine bir id ile getRecipe () işlevini çağırdık ve veri dönene kadar await anahtar sözcüğünü kullanarak bekledik. İşte sonunda geri arama cehenneminden bu şekilde kurtulduk.

  async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

Await'i kullanmak için eşzamansız bir işleve ihtiyacımız olacak, bir söz verebiliriz, bu yüzden sözünü çözmek için kullanın ve sözümüzü reddetmek için katlayın

yukarıdaki örnekten:

 async function getRecipesAw(){
            const IDs = await getIds;
            const recipe = await getRecipe(IDs[2]);
            return recipe;
        }

        getRecipesAw().then(result=>{
            console.log(result);
        }).catch(error=>{
            console.log(error);
        });

0

Callback cehenneminden kaçınılabilecek bir yol, RX'in "geliştirilmiş sürümü" olan FRP'yi kullanmaktır.

Son zamanlarda FRP'yi Sodium( http://sodium.nz/ ) adında iyi bir uygulama bulduğum için kullanmaya başladım .

Tipik bir kod şuna benzer (Scala.js):

def render: Unit => VdomElement = { _ =>
  <.div(
    <.hr,
    <.h2("Note Selector"),
    <.hr,
    <.br,
    noteSelectorTable.comp(),
    NoteCreatorWidget().createNewNoteButton.comp(),
    NoteEditorWidget(selectedNote.updates()).comp(),
    <.hr,
    <.br
  )
}

selectedNote.updates()bir Streamyangın durumunda hangi selectedNode(bir olan Celldeğişiklikler), NodeEditorWidgetsonra buna uygun olarak günceller.

Dolayısıyla, içeriğine bağlı olarak selectedNode Cell, şu anda düzenlenenler Notedeğişecektir.

Bu kod, Callback'leri neredeyse tamamen önler, Cacllback'ler uygulamanın "dış katmanına" / "yüzeyine" itilir, burada durum işleme mantığı dış dünya ile arayüz oluşturur. Verileri dahili durum işleme mantığı (bir durum makinesi uygulayan) içinde yaymak için geri çağırmaya gerek yoktur.

Tam kaynak kodu burada

Yukarıdaki kod parçacığı, aşağıdaki basit Oluşturma / Görüntüleme / Güncelleme örneğine karşılık gelir:

görüntü açıklamasını buraya girin

Bu kod ayrıca sunucuya güncellemeler gönderir, böylece güncellenmiş Varlıklarda yapılan değişiklikler sunucuya otomatik olarak kaydedilir.

Tüm olay yönetimi Streams ve Cells kullanılarak halledilir . Bunlar FRP kavramlarıdır. Geri aramalara yalnızca, kullanıcı girişi, metin düzenleme, bir düğmeye basma, AJAX çağrısı geri dönüşleri gibi FRP mantığının dış dünya ile arayüz oluşturduğu durumlarda gereklidir.

Veri akışı, FRP (Sodyum kitaplığı tarafından uygulanır) kullanılarak bildirimsel bir şekilde açıkça tanımlanır, bu nedenle veri akışını tanımlamak için olay işleme / geri arama mantığına gerek yoktur.

FRP (RX'in daha "katı" bir sürümüdür), durumu içeren düğümler içerebilen bir veri akış grafiğini açıklamanın bir yoludur. Olaylar, düğümleri içeren durumdaki durum değişikliklerini tetikler (Cell ) .

Sodyum, yüksek dereceli bir FRP kitaplığıdır, yani flatMap/switch ilkel olanı kullanarak, çalışma zamanında veri akış grafiğini yeniden düzenleyebilirsiniz.

Sodyum kitabına bir göz atmanızı tavsiye ederim , FRP'nin bazı harici uyaranlara yanıt olarak uygulama durumunu güncellemekle ilgili veri akışı mantığını açıklamak için gerekli olmayan tüm Geri Çağırmalardan nasıl kurtulduğunu ayrıntılı olarak açıklar.

FRP'yi kullanarak, yalnızca dış dünya ile etkileşimi tanımlayan Geri Çağrılar saklanmalıdır. Başka bir deyişle, veri akışı, bir FRP çerçevesi (Sodyum gibi) kullanıldığında veya "FRP benzeri" bir çerçeve (RX gibi) kullanıldığında işlevsel / bildirimsel bir şekilde açıklanır.

Sodyum, Javascript / Typescript için de mevcuttur.


-3

Geri arama ve cehennem geri arama hakkında bir bilginiz yoksa sorun yoktur. İlk şey geri aramak ve cehennemi geri aramaktır.Örneğin: geri arama, bir sınıfı bir sınıfın içinde saklayabiliriz gibi. hakkında C, C ++ dilinde yuvalanmış.Nested Başka bir sınıfın içindeki bir sınıf anlamına gelir.


Cevap, 'Callback cehennemi'nin ne olduğunu gösteren kod parçacığı ve' callback cehennemi'ni kaldırdıktan sonra Rx ile aynı kod parçacığı içeriyorsa daha yararlı olacaktır
rafa

-4

Jazz.js kullanın https://github.com/Javanile/Jazz.js

şu şekilde basitleştiriyor:

    // zincirleme sıralı görev çalıştır
    jj.script ([
        // ilk görev
        function (sonraki) {
            // bu işlemin sonunda 'sonraki' ikinci göreve gelin ve çalıştırın 
            callAsyncProcess1 (sonraki);
        },
      // ikinci görev
      function (sonraki) {
        // bu işlemin sonunda 'sonraki' otuz göreve gelin ve çalıştırın 
        callAsyncProcess2 (sonraki);
      },
      // otuz görev
      function (sonraki) {
        // bu işlemin sonunda 'sonraki' işaretini (varsa) 
        callAsyncProcess3 (sonraki);
      },
    ]);


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.