İşlevi devam ettirmeden önce JavaScript Promise'in çözülmesi için nasıl beklenir?


113

Bazı birim testleri yapıyorum. Test çerçevesi bir sayfayı iFrame'e yükler ve ardından o sayfaya karşı iddiaları çalıştırır. Her test başlamadan önce Promise, iFrame'in onloadolayını çağıracak resolve(), iFrame'leri ayarlayan srcve sözü veren bir tane oluşturuyorum.

Yani, sadece arayabilirim loadUrl(url).then(myFunc)ve ne myFuncolduğunu çalıştırmadan önce sayfanın yüklenmesini bekleyecek .

Bu tür bir kalıbı testlerimin her yerinde kullanıyorum (sadece URL'leri yüklemek için değil), öncelikle DOM'da değişiklik yapılmasına izin vermek için (örneğin, bir düğmeyi tıklatmayı taklit edin ve div'lerin gizlenmesini ve gösterilmesini bekleyin).

Bu tasarımın dezavantajı, sürekli olarak birkaç satır kod içeren anonim işlevler yazıyor olmam. Dahası, bir çözümüm (QUnit'ler assert.async()) varken , vaatleri tanımlayan test işlevi vaat yerine getirilmeden önce tamamlanır.

PromiseNET'lere benzer şekilde çözülene kadar a'dan bir değer almanın veya beklemenin (blok / uyku) bir yolu olup olmadığını merak ediyorum IAsyncResult.WaitHandle.WaitOne(). JavaScript'in tek iş parçacıklı olduğunu biliyorum, ancak umarım bu bir işlevin veremeyeceği anlamına gelmez.

Özünde, sonuçları doğru sırayla bildirmek için aşağıdakileri elde etmenin bir yolu var mı?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


Ek çağrıları yeniden kullanılabilir bir işleve koyarsanız, KURU için gerektiği kadar () yapabilirsiniz. Ayrıca, bu şekilde yönlendirilen çok amaçlı işleyiciler de yapabilirsiniz, daha sonra () çağrıları beslemek için yönlendirilir , örneğin .then(fnAppend.bind(myDiv))anonları büyük ölçüde azaltabilir.
dandavis

Neyle test ediyorsun? Modern bir tarayıcıysa veya kodunuzu dönüştürmek için BabelJS gibi bir araç kullanabilirsiniz, bu kesinlikle mümkündür.
Benjamin Gruenbaum

Yanıtlar:


81

NET'in IAsyncResult.WaitHandle.WaitOne () gibi bir Promise'den bir değer almanın veya çözülene kadar beklemenin (blok / uyku) herhangi bir yolu olup olmadığını merak ediyorum. JavaScript'in tek iş parçacıklı olduğunu biliyorum, ancak umarım bu bir işlevin veremeyeceği anlamına gelmez.

Tarayıcılardaki mevcut Javascript neslinde, başka şeylerin çalışmasına izin veren bir wait()veya yoktur sleep(). Yani, istediğiniz şeyi yapamazsınız. Bunun yerine, işlerini yapacak ve daha sonra işiniz bittiğinde sizi arayacak (sözlerinizi kullandığınız gibi) asenkron operasyonlar var.

Bunun bir kısmı Javascript'in tek iş parçacıklılığından kaynaklanıyor. Tek iş parçacığı dönüyorsa, o eğirme işlemi tamamlanana kadar başka hiçbir Javascript çalıştırılamaz. ES6 yield, bunun gibi bazı işbirlikçi hilelere izin verecek üreteçler ve üreteçler sunar, ancak bunları çok çeşitli yüklü tarayıcılarda kullanmanın epey yoludur (bunlar, JS motorunu kontrol ettiğiniz bazı sunucu tarafı geliştirmelerinde kullanılabilir. kullanılıyor).


Söze dayalı kodun dikkatli yönetimi, birçok eşzamansız işlem için yürütme sırasını kontrol edebilir.

Kodunuzda tam olarak hangi sırayı elde etmeye çalıştığınızı anladığımdan emin değilim, ancak mevcut kickOff()işlevinizi kullanarak böyle bir şey yapabilir ve ardından .then()çağırdıktan sonra ona bir işleyici ekleyebilirsiniz:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

Bu, çıktıyı garantili bir sırayla döndürür - şunun gibi:

start
middle
end

2018'deki güncelleme (bu cevabın yazılmasından üç yıl sonra):

Ya kodunuzu transpile veya destekleri ES7 gibi öne çıkardığına bir ortamda kodunuzu çalıştırırsanız asyncve awaitartık kullanabilirsiniz awaitkodunuzu "görünür" hale getirmek için bir söz sonucu beklemek. Hala vaatlerle gelişiyor. Yine de Javascript'in tamamını engellemez, ancak daha kolay bir sözdiziminde sıralı işlemler yazmanıza izin verir.

İşleri yapmanın ES6 yolu yerine:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

Bunu yapabilirsiniz:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

3
Doğru, kullanmak then()benim yaptığım şey. Her function() { ... }zaman yazmaktan hoşlanmıyorum . Kodu karıştırır.
dx_over_dt

3
@dfoverdx - Javascript'teki eşzamansız kodlama her zaman geri aramaları içerir, bu nedenle her zaman bir işlev (anonim veya adlandırılmış) tanımlamanız gerekir. Şu anda etrafından dolaşmak yok.
jfriend00

2
Yine de daha kısa yapmak için ES6 ok gösterimini kullanabilirsiniz. (foo) => { ... }yerinefunction(foo) { ... }
Rob H

1
@RobH yorumuna sıklıkla ekleyerek foo => single.expression(here), kıvrık ve yuvarlak parantezlerden kurtulmak için de yazabilirsiniz .
begie

3
@dfoverdx - Cevabımdan üç yıl sonra , şimdi node.js'de ve modern tarayıcılarda veya aktarıcılar aracılığıyla kullanılabilen bir seçenek olarak ES7 asyncve awaitsözdizimi ile güncelledim .
jfriend00

14

ES2016 kullanıyorsanız kullanabileceğiniz asyncve awaitve böyle bir şey yapmak:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

ES2015 kullanıyorsanız, Jeneratörleri kullanabilirsiniz . Sözdizimini beğenmezseniz, burada açıklandığı gibi bir asyncyardımcı program işlevi kullanarak onu soyutlayabilirsiniz .

ES5 kullanıyorsanız, muhtemelen size daha fazla kontrol vermesi için Bluebird gibi bir kitaplık isteyeceksiniz .

Son olarak, çalışma zamanınız ES2015'i destekliyorsa, zaten yürütme sırası Getirme Enjeksiyonu kullanılarak paralellik ile korunabilir .


1
Jeneratör örneğiniz çalışmıyor, bir koşucusu yok. ES8 async/ ' i aktarabileceğiniz zaman lütfen artık üreteçleri önermeyin await.
Bergi

3
Geri bildiriminiz için teşekkürler. Generator örneğini kaldırdım ve bunun yerine ilgili OSS kitaplığıyla daha kapsamlı bir Generator eğitimine bağlandım.
Josh Habdas

12
Bu sadece sözü tamamlar. Zaman uyumsuz işlevi olacak değil bir şey için bunu çalıştırdığınızda, sadece bir söz dönecektir bekleyin. async/ awaitiçin başka bir sözdizimi Promise.then.
rustyx

Kesinlikle haklısın async, beklemeyeceksin ... kullanmaktan vazgeçmedikçeawait . ES2016 / ES7 örneği hala SO çalıştırılamıyor, bu yüzden işte üç yıl önce yazdığım davranışı gösteren bazı kodlar .
Josh Habdas

8

Başka bir seçenek de Promise.all'ı kullanarak bir dizi vaatin çözülmesini beklemek ve ardından bunlara göre hareket etmektir.

Aşağıdaki kod, tüm vaatlerin çözüme kavuşturulmasını nasıl bekleyeceğinizi ve daha sonra hepsi hazır olduklarında sonuçlarla nasıl başa çıkılacağını göstermektedir (çünkü sorunun amacı bu gibi görünmektedir); Ayrıca açıklama amacıyla, yürütme sırasındaki çıktıyı gösterir (ortadan önce bitişler).

function append_output(suffix, value) {
  $("#output_"+suffix).append(value)
}

function kickOff() {
  let start = new Promise((resolve, reject) => {
    append_output("now", "start")
    resolve("start")
  })
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => {
      append_output("now", " middle")
      resolve(" middle")
    }, 1000)
  })
  let end = new Promise((resolve, reject) => {
    append_output("now", " end")
    resolve(" end")
  })

  Promise.all([start, middle, end]).then(results => {
    results.forEach(
      result => append_output("later", result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Updated during execution: <div id="output_now"></div>
Updated after all have completed: <div id="output_later"></div>


Bu da işe yaramayacak. Bu sadece sözlerin sırayla yürütülmesini sağlar. KickOff () 'dan sonraki herhangi bir şey sözler bitmeden çalışacaktır.
Philip Rego

@PhilipRego - doğru, daha sonra çalıştırmak istediğiniz herhangi bir şeyin Promises.all'da olması gerekir, sonra engelleyin. Yürütme sırasında çıktıya eklenecek kodu güncelledim (ortadan önce son baskılar - vaatler sıra dışı kalabilir), ancak daha sonra blok TÜMÜNÜN bitmesini bekler ve ardından sonuçları sırayla ele alır.
Stan Kurdziel

BTW: Bu keman çalıp şu kodla oynayabilirsiniz: jsfiddle.net/akasek/4uxnkrez/12
Stan Kurdziel

-1

Bunu manuel olarak yapabilirsiniz. (Bunun harika bir çözüm olmadığını biliyorum, ancak ..) bir değeri whileolmayana kadar döngü kullanınresult

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

2
bu, beklerken tüm cpu'yu tüketir.
Lukasz Guminski

bu korkunç bir çözüm
JSON

Ya ama bu, işlevleri veya vaatleri iç içe geçirmek zorunda kalmadan çalışmanın tek yoludur. Bu durumda bile, iç içe geçmiş işlevler / vaatler tamamlanmadan önce çalıştırılır.
Philip Rego

Bu işe yaramayacak. resultDeğişmeyi bekleyen sonsuz bir döngüde olduğunuz için , olay döngüsü başka hiçbir şeyi işleyemeyeceği için sonucun asla değişme fırsatı olmayacaktır. Ve resultzaten hiçbir zaman değişmeyen bir fonksiyon argümanıdır.
jfriend00
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.