"Dönüş bekleme sözü" ve "iade sözü" arasındaki fark


116

Aşağıdaki kod örnekleri göz önüne alındığında, davranışta herhangi bir fark var mı ve eğer öyleyse, bu farklılıklar nelerdir?

return await promise

async function delay1Second() {
  return (await delay(1000));
}

return promise

async function delay1Second() {
  return delay(1000);
}

Anladığım kadarıyla, birincisi zaman uyumsuz işlev içinde hata işlemeye sahip olacaktı ve hatalar eşzamansız işlevin Promise'inden fırlayacaktı. Bununla birlikte, ikincisi bir eksik tik gerektirecektir. Bu doğru mu?

Bu pasaj, referans için bir Promise döndürmek için yalnızca yaygın bir işlevdir.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

3
Evet, sorumu düzenledim çünkü anlamımı yanlış anladınız ve merak ettiğim şeyi gerçekten cevaplamadı.
PitaJ

1
@PitaJ: asyncİkinci ( return promise) örneğinizden kaldırmak istediğinize inanıyorum .
Stephen Cleary

1
@PitaJ: Bu durumda, ikinci örneğiniz bir sözle çözülmüş bir söz verir. Oldukça tuhaf.
Stephen Cleary

5
jakearchibald.com/2017/await-vs-return-vs-return-await , farklılıkları özetleyen güzel bir makale
sanchit

2
@StephenCleary, buna tökezledim ve ilk önce tamamen aynı şeyi düşündüm, bir sözle çözülen bir söz burada mantıklı değil. Ama döndükçe, promise.then(() => nestedPromise)düzleşir ve nestedPromise. C # 'da yapmamız gereken iç içe geçmiş görevlerden farkı ilginç Unwrap. Bir yan notta, sadece bazı ilginç anlamsal farklılıklarla değil , await somePromise çağırıyor gibi görünüyor . Promise.resolve(somePromise).thensomePromise.then
noseratio

Yanıtlar:


165

Çoğu zaman returnve arasında gözlemlenebilir bir fark yoktur return await. Her iki sürüm de delay1Secondaynı gözlemlenebilir davranışa sahiptir (ancak uygulamaya bağlı olarak return awaitsürüm biraz daha fazla bellek kullanabilir, çünkü bir ara Promisenesne oluşturulabilir).

@PitaJ belirttiği gibi Ancak, bir fark var bir durum vardır: eğer returnyoksa return awaitbir iç içe try- catchbloğun. Bu örneği düşünün

async function rejectionWithReturnAwait () {
  try {
    return await Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

async function rejectionWithReturn () {
  try {
    return Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

İlk versiyonda, async işlevi, sonucunu döndürmeden önce reddedilen sözü beklemektedir, bu da reddin istisnaya dönüştürülmesine ve catchcümleye ulaşılmasına neden olur; işlev böylece "Kaydedildi!" dizesine çözümlenen bir söz döndürecektir.

Fonksiyonunun ikinci versiyonu Ancak, doğrudan reddedilen söz dönmek yok zaman uyumsuz fonksiyonu içinde bunu beklemeden demek olduğunu, catchvaka olduğu değil adlandırılan ve arayan yerine reddi alır.


Belki yığın izlemenin farklı olacağını da söyleyebilirsiniz (dene / yakala olmasa bile)? Sanırım bu örnekte insanların en sık karşılaştığı sorun budur:]
Benjamin Gruenbaum

Bir senaryoda, return new Promise(function(resolve, reject) { })bir for...ofdöngü içinde kullanıp daha sonra döngü resolve()içinde çağırmanın , pipe()boru tamamlanıncaya kadar program yürütmesini duraklatmadığını, ancak kullanılması gerektiği gibi buldum await new Promise(...). ikincisi bile geçerli / doğru sözdizimi mi? için 'steno' return await new Promise(...)mu? İkincisinin neden çalıştığını ve birincisinin neden çalışmadığını anlamama yardım edebilir misin? bağlam için, senaryo ise solution 02bir bu cevap
user1063287

14

Diğer cevapların da belirtildiği gibi, sözün doğrudan geri dönerek kabarmasına izin verirken muhtemelen küçük bir performans avantajı vardır - çünkü önce sonucu beklemek ve sonra tekrar başka bir sözle paketlemek zorunda değilsiniz. Ancak, henüz kimse kuyruk arama optimizasyonundan bahsetmedi .

Kuyruk arama optimizasyonu veya "uygun kuyruk aramaları" , tercümanın çağrı yığınını optimize etmek için kullandığı bir tekniktir. Şu anda, pek çok çalışma zamanı henüz desteklemiyor - teknik olarak ES6 Standardının bir parçası olsa da - ancak gelecekte olası bir destek eklenebilir, böylece şimdiki zamanda iyi bir kod yazarak buna hazırlanabilirsiniz.

Özetle, TCO (veya PTC) çağrı yığını optimize değil , doğrudan başka bir işlev tarafından döndürülen bir işlev için yeni bir çerçeve açılması. Bunun yerine, aynı çerçeveyi yeniden kullanır.

async function delay1Second() {
  return delay(1000);
}

Bu yana delay(), doğrudan tarafından döndürülen delay1Second(), PTC için ilk bir çerçeve açılır destek çalışma zamanları delay1Second()(dış fonksiyonu), ancak daha sonra yerine açma bir çerçevesini delay()(iç fonksiyonu), sadece dış işlev için açılmıştır aynı çerçeve yeniden. Bir önleyebilir, bu yığın optimize bellek taşması , örneğin çok büyük yinelemeli fonksiyonları ile (hehe) fibonacci(5e+25). Esasen çok daha hızlı bir döngü haline gelir.

PTC yalnızca iç işlev doğrudan döndürüldüğünde etkinleştirilir . İşlevin sonucu döndürülmeden önce değiştirildiğinde, örneğin return (delay(1000) || null), veya varsa kullanılmaz return await delay(1000).

Ancak dediğim gibi, çoğu çalışma zamanı ve tarayıcı henüz PTC'yi desteklemiyor, bu yüzden muhtemelen şu anda büyük bir fark yaratmıyor, ancak kodunuzu geleceğe hazırlamanıza zarar veremez.

Bu soruda daha fazlasını okuyun: Node.js: Eşzamansız işlevlerde kuyruk çağrıları için optimizasyonlar var mı?


3

Bu cevaplanması zor bir sorudur çünkü pratikte aktarıcınızın (muhtemelen babel) gerçekte nasıl işlediğine bağlıdır async/await. Ne olursa olsun net olan şeyler:

  • Her iki uygulama da aynı şekilde davranmalıdır, ancak ilk uygulama zincirde bir tane eksik olabilirPromise .

  • Özellikle gereksiz awaitolanı bırakırsanız, ikinci sürüm transpiler'dan ekstra kod gerektirmezken, birincisi yapar.

Bu nedenle, bir kod performansı ve hata ayıklama perspektifinden, ikinci sürüm tercih edilir, ancak çok az da olsa, ilk sürümün hafif bir okunabilirlik avantajı vardır, çünkü açıkça bir söz verdiğini gösterir.


Neden işlevler aynı şekilde davranır? İlki çözümlenmiş bir değer ( undefined) döndürür ve ikincisi a Promise.
Amit

4
@ Her iki işlevin de bir Söz döndürmesini kabul edin
PitaJ

Ack. Bu yüzden tahammül edemiyorum async/await- akıl yürütmeyi çok daha zor buluyorum. @PitaJ doğru, her iki işlev de bir Söz verir.
nrabinowitz

Her iki eşzamansız işlevin gövdesini bir ile çevrelemem gerekirse ne olur try-catch? Bu return promisedurumda, herhangi rejectionbiri yakalanmaz, doğru, oysa, return await promisedurumda, olur, değil mi?
PitaJ

Her ikisi de bir Söz verir, ancak ilki ilkel bir değer "vaat eder" ve ikincisi bir Söz "vaat eder". awaitBunların her birini bir arama sitesinde yaparsanız , sonuç çok farklı olacaktır.
Amit

2

Dikkat çeken fark: Söz reddi farklı yerlerde ele alınır

  • return somePromisegeçecek somePromise çağrı sitesine ve await somePromise (eğer varsa) çağrısı yerinde yerleşmek için. Bu nedenle, eğer somePromise reddedilirse, yerel yakalama bloğu tarafından değil, çağrı sitesinin yakalama bloğu tarafından işlenecektir.

async function foo () {
  try {
    return Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'OUT'

  • return await somePromiseilk önce yerel olarak yerleşmek için bazı Sözleri bekleyecek . Bu nedenle, değer veya İstisna önce yerel olarak ele alınacaktır. => somePromiseReddedilirse yerel yakalama bloğu çalıştırılacaktır .

async function foo () {
  try {
    return await Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'IN'

Sebep: return await PromiseHem yerel hem de dışarıda return Promisebekliyor, sadece dışarıda bekliyor

Ayrıntılı Adımlar:

dönüş söz

async function delay1Second() {
  return delay(1000);
}
  1. çağrı delay1Second();
const result = await delay1Second();
  1. İçeride delay1Second(), işlev delay(1000)ile hemen bir söz verir [[PromiseStatus]]: 'pending. Hadi diyelim delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Zaman uyumsuz işlevler, dönüş değerlerini Promise.resolve()( Source ) içine kaydırır . Çünkü delay1Secondbir zaman uyumsuz fonksiyonudur elimizde:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)delayPromisegirdi zaten bir söz olduğundan hiçbir şey yapmadan döner (bkz. MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitdelayPromiseyerleşene kadar bekler .
  • IF delayPromisePromiseValue = 1 ile yerine getirilir:
const result = 1; 
  • ELSE delayPromisereddedildi:
// jump to catch block if there is any

Geri dön Bekle Söz

async function delay1Second() {
  return await delay(1000);
}
  1. çağrı delay1Second();
const result = await delay1Second();
  1. İçeride delay1Second(), işlev delay(1000)ile hemen bir söz verir [[PromiseStatus]]: 'pending. Hadi diyelim delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Yerel bekleme delayPromise, yerleşene kadar bekleyecek .
  • Durum 1 : delayPromisePromiseValue = 1 ile yerine getirildi:
async function delay1Second() {
  return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise; 
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1; 
  • Durum 2 : delayPromisereddedildi:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;

Sözlük:

  • Settle: Promise.[[PromiseStatus]]dan değişiklikler pendingiçin resolvedveyarejected

0

burada sizin için pratik bir kod bırakıyorum, farkı anlayabilirsiniz

 let x = async function () {
  return new Promise((res, rej) => {
    setTimeout(async function () {
      console.log("finished 1");
      return await new Promise((resolve, reject) => { // delete the return and you will see the difference
        setTimeout(function () {
          resolve("woo2");
          console.log("finished 2");
        }, 5000);
      });
      res("woo1");
    }, 3000);
  });
};

(async function () {
  var counter = 0;
  const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
    if (counter == 7) {
      clearInterval(a);
    }

    console.log(counter);
    counter = counter + 1;
  }, 1000);
  console.time("time1");
  console.log("hello i starting first of all");
  await x();
  console.log("more code...");
  console.timeEnd("time1");
})();

"x" işlevi, diğer bir fucn işlevine göre zaman uyumsuz bir işlevdir, eğer geri dönüşü silecekse "daha fazla kod ..."

x değişkeni, sırayla başka bir eşzamansız işleve sahip olan eşzamansız bir işlevdir, kodun ana kısmında, x değişkeninin işlevini çağırmak için bir bekleme çağırırız, tamamlandığında, kodun sırasını izler, bu normal olur "async / await" için, ancak x işlevinin içinde başka bir eşzamansız işlev vardır ve bu bir söz döndürür veya bir "söz" döndürür, ana kodu unutarak x işlevi içinde kalacaktır, yani, "console.log (" daha fazla kod .. "), öte yandan," beklemek "koyarsak, tamamlanan ve sonunda ana kodun normal sırasını izleyen her işlevi bekleyecektir.

"console.log (" bitti 1 "'in altında" return "i silerseniz, davranışı göreceksiniz.


1
Bu kod soruyu çözebilirken, sorunun nasıl ve neden çözüldüğüne dair bir açıklama da dahil olmak üzere , gönderinizin kalitesini artırmaya gerçekten yardımcı olur ve muhtemelen daha fazla oy almanıza neden olur. Sadece şu anda soran kişi için değil, gelecekte okuyucular için soruyu yanıtladığınızı unutmayın. Açıklamalar eklemek ve hangi sınırlamaların ve varsayımların geçerli olduğuna dair bir gösterge vermek için lütfen yanıtınızı düzenleyin .
Brian

0

İşte çalıştırabileceğiniz ve kendinizi "dönüş beklemeye" ihtiyacınız olduğuna ikna edebileceğiniz bir tipkript örneği.

async function  test() {
    try {
        return await throwErr();  // this is correct
        // return  throwErr();  // this will prevent inner catch to ever to be reached
    }
    catch (err) {
        console.log("inner catch is reached")
        return
    }
}

const throwErr = async  () => {
    throw("Fake error")
}


void test().then(() => {
    console.log("done")
}).catch(e => {
    console.log("outer catch is reached")
});

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.