Perbedaan antara `return await promise` dan` return promise`

108

Dengan contoh kode di bawah ini, apakah ada perbedaan dalam perilakunya, dan, jika ya, apa perbedaannya?

return await promise

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

return promise

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

Seperti yang saya pahami, yang pertama akan memiliki penanganan kesalahan dalam fungsi async, dan kesalahan akan menggelembung keluar dari Janji fungsi asinkron. Namun, yang kedua membutuhkan satu centang lebih sedikit. Apakah ini benar?

Cuplikan ini hanyalah fungsi umum untuk mengembalikan Janji sebagai referensi.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
PitaJ
sumber
3
Ya, saya mengedit pertanyaan saya karena Anda salah memahami maksud saya dan pertanyaan itu tidak benar-benar menjawab pertanyaan saya.
PitaJ
1
@ PitaJ: Saya yakin Anda bermaksud menghapus asyncdari return promisesampel kedua ( ) Anda.
Stephen Cleary
1
@PitaJ: Dalam hal ini, contoh kedua Anda akan mengembalikan janji yang diselesaikan dengan sebuah janji. Agak aneh.
Stephen Cleary
5
jakearchibald.com/2017/await-vs-return-vs-return-await adalah artikel bagus yang merangkum perbedaan
sanchit
2
@StephenCleary, saya menemukan ini dan pertama kali berpikir persis sama, janji yang diselesaikan dengan janji tidak masuk akal di sini. Tapi karena ternyata, promise.then(() => nestedPromise)akan meratakan dan "mengikuti" itu nestedPromise. Menarik bagaimana ini berbeda dari tugas bersarang di C # di mana kita harus melakukannya Unwrap. Di samping catatan, tampaknya await somePromise panggilan Promise.resolve(somePromise).then, bukan hanya somePromise.then, dengan beberapa perbedaan semantik yang menarik.
noseratio

Jawaban:

155

Sering kali, tidak ada perbedaan yang dapat diamati antara returndan return await. Kedua versi delay1Secondmemiliki perilaku observasi yang sama persis (tetapi bergantung pada implementasinya, return awaitversi tersebut mungkin menggunakan lebih banyak memori karena Promiseobjek perantara mungkin dibuat).

Namun, seperti yang ditunjukkan @PitaJ, ada satu kasus di mana terdapat perbedaan: jika returnatau return awaitberada di dalam blok try- catch. Perhatikan contoh ini

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!'
  }
}

Pada versi pertama, fungsi async menunggu janji yang ditolak sebelum mengembalikan hasilnya, yang menyebabkan penolakan tersebut diubah menjadi pengecualian dan catchklausa tercapai; fungsi ini kemudian akan mengembalikan sebuah janji yang diselesaikan ke string "Tersimpan!".

Namun, versi kedua dari fungsi ini mengembalikan janji yang ditolak secara langsung tanpa menunggunya di dalam fungsi async , yang berarti catchkasus tidak dipanggil dan pemanggil mendapat penolakan.

Denis Washington
sumber
Mungkin juga menyebutkan bahwa pelacakan tumpukan akan berbeda (bahkan tanpa mencoba / menangkap)? Saya pikir itulah masalah yang paling sering
dialami orang
Saya telah menemukan dalam satu skenario, bahwa menggunakan return new Promise(function(resolve, reject) { })dalam satu for...ofloop dan kemudian memanggil resolve()dalam loop setelah pipe()tidak menghentikan eksekusi program sampai pipa telah selesai, seperti yang diinginkan, namun menggunakan await new Promise(...)tidak. apakah sintaks yang terakhir valid / benar? apakah ini 'singkatan' untuk return await new Promise(...)? dapatkah Anda membantu saya memahami mengapa yang terakhir berhasil dan yang pertama tidak? untuk konteks, skenario dalam solution 02dari jawaban ini
user1063287
12

Seperti jawaban lain yang disebutkan, kemungkinan ada sedikit manfaat kinerja saat membiarkan janji meluap dengan mengembalikannya secara langsung - hanya karena Anda tidak harus menunggu hasilnya terlebih dahulu dan kemudian membungkusnya dengan janji lain lagi. Namun, belum ada yang berbicara tentang pengoptimalan panggilan ekor .

Pengoptimalan panggilan ekor , atau "panggilan ekor yang tepat" , adalah teknik yang digunakan interpreter untuk mengoptimalkan tumpukan panggilan. Saat ini, belum banyak runtime yang mendukungnya - meskipun secara teknis merupakan bagian dari Standar ES6 - tetapi kemungkinan dukungan itu mungkin ditambahkan di masa mendatang, sehingga Anda dapat mempersiapkannya dengan menulis kode yang baik saat ini.

Singkatnya, TCO (atau PTC) mengoptimalkan tumpukan panggilan dengan tidak membuka bingkai baru untuk fungsi yang langsung dikembalikan oleh fungsi lain. Sebaliknya, itu menggunakan kembali bingkai yang sama.

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

Karena delay()langsung dikembalikan oleh delay1Second(), runtime yang mendukung PTC pertama-tama akan membuka bingkai untuk delay1Second()(fungsi luar), tetapi alih-alih membuka bingkai lain untuk delay()(fungsi bagian dalam), itu hanya akan menggunakan kembali bingkai yang sama yang dibuka untuk fungsi luar. Ini mengoptimalkan stack karena dapat mencegah stack overflow (hehe) dengan fungsi rekursif yang sangat besar, mis fibonacci(5e+25). Pada dasarnya ini menjadi loop, yang jauh lebih cepat.

PTC hanya diaktifkan ketika fungsi bagian dalam langsung dikembalikan. Ini tidak digunakan ketika hasil dari fungsi diubah sebelum dikembalikan, misalnya, jika Anda memiliki return (delay(1000) || null), atau return await delay(1000).

Tapi seperti yang saya katakan, sebagian besar runtime dan browser belum mendukung PTC, jadi mungkin sekarang tidak membuat perbedaan besar, tetapi tidak ada salahnya untuk membuktikan kode Anda di masa mendatang.

Baca lebih lanjut dalam pertanyaan ini: Node.js: Apakah ada pengoptimalan untuk panggilan ekor dalam fungsi asinkron?

chharvey
sumber
2

Ini adalah pertanyaan yang sulit untuk dijawab, karena ini tergantung pada bagaimana transpiler Anda (mungkin babel) benar-benar ditampilkan async/await. Hal-hal yang jelas:

  • Kedua implementasi harus berperilaku sama, meskipun implementasi pertama mungkin memiliki satu implementasi yang lebih sedikit Promisedi rantai.

  • Terutama jika Anda membuang yang tidak diperlukan await, versi kedua tidak akan memerlukan kode tambahan dari transpiler, sedangkan versi pertama memerlukannya.

Jadi dari perspektif kinerja kode dan debugging, versi kedua lebih disukai, meskipun hanya sedikit, sedangkan versi pertama memiliki sedikit manfaat keterbacaan, karena dengan jelas menunjukkan bahwa ia mengembalikan sebuah janji.

nrabinowitz
sumber
Mengapa fungsinya berperilaku sama? Yang pertama mengembalikan nilai terselesaikan ( undefined) dan yang kedua mengembalikan a Promise.
Amit
4
@Amit kedua fungsi mengembalikan Janji
PitaJ
Ack. Inilah mengapa saya tidak tahan async/await- saya merasa jauh lebih sulit untuk bernalar. @PitaJ benar, kedua fungsi mengembalikan Promise.
nrabinowitz
Bagaimana jika saya mengelilingi tubuh dari kedua fungsi asinkron dengan a try-catch? Dalam return promisekasus ini, rejectiontidak akan ada yang tertangkap, benar, sedangkan dalam return await promisekasus, akan tertangkap , bukan?
PitaJ
Keduanya mengembalikan sebuah Janji, tetapi yang pertama "menjanjikan" nilai primitif, dan yang kedua "menjanjikan" sebuah Janji. Jika Anda awaitmasing-masing di beberapa situs panggilan, hasilnya akan sangat berbeda.
Amit
1

Perbedaan nyata: Penolakan janji ditangani di tempat yang berbeda

  • return somePromiseakan memberikan somePromise ke situs panggilan, dan await somePromise untuk menetap di situs panggilan (jika ada). Oleh karena itu, jika somePromise ditolak, itu tidak akan ditangani oleh blok catch lokal, tetapi blok catch situs panggilan itu.

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 somePromisepertama-tama akan menunggu beberapa janji untuk menetap secara lokal. Oleh karena itu, nilai atau Pengecualian pertama-tama akan ditangani secara lokal. => Blok tangkapan lokal akan dieksekusi jika somePromiseditolak.

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'

Alasan: return await Promisemenunggu baik di dalam maupun di luar, return Promisehanya menunggu di luar

Langkah Terperinci:

kembali Janji

async function delay1Second() {
  return delay(1000);
}
  1. panggilan delay1Second();
const result = await delay1Second();
  1. Di dalam delay1Second(), fungsi delay(1000)mengembalikan sebuah promise langsung dengan [[PromiseStatus]]: 'pending. Sebut saja delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Fungsi Async akan membungkus nilai kembaliannya di dalam Promise.resolve()( Sumber ). Karena delay1Secondmerupakan fungsi async, kami memiliki:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)kembali delayPromisetanpa melakukan apa pun karena inputnya sudah menjadi promise (lihat MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitmenunggu sampai delayPromisediselesaikan.
  • JIKA delayPromisedipenuhi dengan PromiseValue = 1:
const result = 1; 
  • ELSE delayPromiseditolak:
// jump to catch block if there is any

kembali menunggu Janji

async function delay1Second() {
  return await delay(1000);
}
  1. panggilan delay1Second();
const result = await delay1Second();
  1. Di dalam delay1Second(), fungsi delay(1000)mengembalikan sebuah promise langsung dengan [[PromiseStatus]]: 'pending. Sebut saja delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Lokal menunggu akan menunggu sampai delayPromisediselesaikan.
  • Kasus 1 : delayPromisedipenuhi dengan PromiseValue = 1:
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; 
  • Kasus 2 : delayPromiseditolak:
// 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;

Glosarium:

  • Selesaikan: Promise.[[PromiseStatus]]perubahan dari pendingmenjadi resolvedataurejected
Ragtime
sumber
0

Di sini saya meninggalkan beberapa kode praktis agar Anda dapat memahami perbedaannya

 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");
})();

fungsi "x" hanya adalah fungsi async daripada memiliki fucn lain jika akan menghapus kembali mencetak "lebih banyak kode ..."

variabel x hanyalah sebuah fungsi asynchronous yang pada gilirannya memiliki fungsi asynchronous lain, di kode utama kita memanggil menunggu untuk memanggil fungsi variabel x, ketika selesai maka mengikuti urutan kode, itu akan normal untuk "async / await", tetapi di dalam fungsi x terdapat fungsi asinkron lainnya, dan ini mengembalikan sebuah janji atau mengembalikan sebuah "janji" itu akan tetap berada di dalam fungsi x, melupakan kode utama, yaitu, tidak akan mencetak "console.log (" more code .. "), sebaliknya jika kita meletakkan" await "maka akan menunggu setiap fungsi selesai dan akhirnya mengikuti urutan normal dari kode utama.

di bawah "console.log (" finish 1 "hapus" return ", Anda akan melihat perilakunya.

Carlos Terrazas
sumber
1
Meskipun kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah akan sangat membantu meningkatkan kualitas posting Anda, dan mungkin menghasilkan lebih banyak suara. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang. Harap edit jawaban Anda untuk menambahkan penjelasan dan memberikan indikasi batasan dan asumsi apa yang berlaku.
Brian
0

Berikut adalah contoh skrip ketikan yang dapat Anda jalankan dan yakinkan diri Anda sendiri bahwa Anda perlu "kembali menunggu"

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")
});

David Dehghan
sumber