coba / tangkap blok dengan async / await

116

Saya sedang menggali fitur node 7 async / await dan terus menemukan kode seperti ini

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

Tampaknya ini satu-satunya kemungkinan menyelesaikan / menolak atau mengembalikan / melempar dengan async / await, namun, v8 tidak mengoptimalkan kode dalam blok coba / tangkap ?!

Apakah ada alternatif lain?

Patrick
sumber
Apa artinya 'melempar setelah menunggu tidak berhasil'? Jika ada kesalahan? Jika tidak mengembalikan hasil yang diharapkan? Anda bisa melempar kembali di blok tangkapan.
DevDig
afaik v8 melakukan optimasi coba / tangkap, pernyataan lemparan adalah yang lambat
Tamas Hegedus
1
Saya masih tidak mengerti pertanyaannya. Anda tidak menggunakan rantai janji lama, tapi saya tidak berpikir itu akan lebih cepat. Jadi Anda prihatin dengan performa coba-tangkap? Lalu apa hubungannya dengan async menunggu?
Tamas Hegedus
Periksa jawaban saya, saya mencoba untuk mendapatkan pendekatan yang lebih bersih
zardilior
Di sini Anda dapat melakukan stackoverflow.com/a/61833084/6482248 Terlihat lebih bersih
Prathamesh Selengkapnya

Jawaban:

133

Alternatif

Alternatif untuk ini:

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

akan menjadi seperti ini, menggunakan promise secara eksplisit:

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}

atau sesuatu seperti ini, menggunakan gaya passing lanjutan:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

Contoh asli

Apa yang dilakukan kode asli Anda adalah menangguhkan eksekusi dan menunggu janji dikembalikan getQuote()untuk diselesaikan. Kemudian melanjutkan eksekusi dan menulis nilai yang dikembalikan ke var quotedan kemudian mencetaknya jika promise telah diselesaikan, atau melontarkan pengecualian dan menjalankan blok catch yang mencetak error jika promise tersebut ditolak.

Anda dapat melakukan hal yang sama menggunakan Promise API secara langsung seperti pada contoh kedua.

Performa

Sekarang, untuk pertunjukannya. Mari kita uji!

Saya baru saja menulis kode ini - f1()memberikan 1nilai pengembalian, f2()melempar 1sebagai pengecualian:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Sekarang mari kita panggil kode yang sama jutaan kali, pertama dengan f1():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f1();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

Dan kemudian mari beralih f1()ke f2():

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f2();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

Inilah hasil yang saya dapat f1:

$ time node throw-test.js 
1000000

real    0m0.073s
user    0m0.070s
sys     0m0.004s

Inilah yang saya dapatkan untuk f2:

$ time node throw-test.js 
1000000

real    0m0.632s
user    0m0.629s
sys     0m0.004s

Tampaknya Anda dapat melakukan sesuatu seperti 2 juta lemparan per detik dalam satu proses utas tunggal. Jika Anda melakukan lebih dari itu, Anda mungkin perlu mengkhawatirkannya.

Ringkasan

Saya tidak akan mengkhawatirkan hal-hal seperti itu di Node. Jika hal-hal seperti itu sering digunakan maka itu akan dioptimalkan pada akhirnya oleh tim V8 atau SpiderMonkey atau Chakra dan semua orang akan mengikutinya - ini tidak seperti itu tidak dioptimalkan sebagai prinsip, itu hanya bukan masalah.

Bahkan jika itu tidak dioptimalkan maka saya masih berpendapat bahwa jika Anda memaksimalkan CPU Anda di Node maka Anda mungkin harus menulis nomor Anda berderak di C - itulah tujuan dari add-on asli, antara lain. Atau mungkin hal-hal seperti node.native akan lebih cocok untuk pekerjaan itu daripada Node.js.

Saya bertanya-tanya apa yang akan menjadi kasus penggunaan yang perlu membuang begitu banyak pengecualian. Biasanya melempar pengecualian daripada mengembalikan nilai adalah pengecualian.

rsp
sumber
Saya tahu kodenya dapat dengan mudah ditulis dengan Promises, seperti yang disebutkan, saya telah melihatnya di berbagai contoh, itulah mengapa saya bertanya. Memiliki satu operasi dalam coba / tangkap mungkin tidak menjadi masalah, tetapi beberapa fungsi async / await dengan logika aplikasi lebih lanjut mungkin menjadi masalah.
Patrick
4
@ Patrick "mungkin" dan "akan" adalah perbedaan antara spekulasi dan pengujian. Saya mengujinya untuk satu pernyataan karena itulah yang ada dalam pertanyaan Anda tetapi Anda dapat dengan mudah mengubah contoh saya untuk menguji beberapa pernyataan. Saya juga menyediakan beberapa opsi lain untuk menulis kode asinkron yang juga Anda tanyakan. Jika itu menjawab pertanyaan Anda, maka Anda dapat mempertimbangkan untuk menerima jawabannya . Singkatnya: tentu saja pengecualian lebih lambat daripada pengembalian tetapi penggunaannya harus menjadi pengecualian.
rsp
1
Melempar pengecualian memang seharusnya menjadi pengecualian. Karena itu, kode tersebut tidak dioptimalkan apakah Anda membuat pengecualian atau tidak. Hit kinerja berasal dari penggunaan try catch, bukan dari melempar pengecualian. Meskipun angkanya kecil, ini hampir 10 kali lebih lambat menurut pengujian Anda, dan itu tidak signifikan.
Nepoxx
22

Alternatif Serupa Dengan Penanganan Kesalahan Di Golang

Karena async / await menggunakan promise, Anda dapat menulis sedikit fungsi utilitas seperti ini:

export function catchEm(promise) {
  return promise.then(data => [null, data])
    .catch(err => [err]);
}

Kemudian impor setiap kali Anda perlu menemukan beberapa kesalahan, dan bungkus fungsi async Anda yang mengembalikan sebuah janji dengannya.

import catchEm from 'utility';

async performAsyncWork() {
  const [err, data] = await catchEm(asyncFunction(arg1, arg2));
  if (err) {
    // handle errors
  } else {
    // use data
  }
}
Steve Banton
sumber
Saya membuat paket NPM yang melakukan persis seperti di atas - npmjs.com/package/@simmo/task
Mike
2
@Mike Anda mungkin menemukan kembali roda - sudah ada paket populer yang melakukan hal ini: npmjs.com/package/await-to-js
Jakub Kukul
21

Alternatif untuk blok try-catch adalah await-to-js lib. Saya sering menggunakannya. Sebagai contoh:

import to from 'await-to-js';

async function main(callback) {
    const [err,quote] = await to(getQuote());
    
    if(err || !quote) return callback(new Error('No Quote found'));

    callback(null,quote);

}

Sintaks ini jauh lebih bersih jika dibandingkan dengan try-catch.

Pulkit chadha
sumber
Sudah mencoba ini dan menyukainya. Kode bersih, dan dapat dibaca dengan mengorbankan memasang modul baru. Tetapi jika Anda berencana untuk menulis banyak fungsi asinkron, saya harus mengatakan ini adalah tambahan yang bagus! Terima kasih
filipbarak
15
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Alternatifnya, alih-alih mendeklarasikan kemungkinan var untuk menahan kesalahan di atas, Anda dapat melakukannya

if (quote instanceof Error) {
  // ...
}

Meskipun itu tidak akan berhasil jika sesuatu seperti TypeError atau Reference error dilemparkan. Anda dapat memastikan itu adalah kesalahan biasa dengan

async function main() {
  var quote = await getQuote().catch(err => {
    console.error(err)      

    return new Error('Error getting quote')
  })

  if (quote instanceOf Error) return quote // get out of here or do whatever

  console.log(quote)
}

Preferensi saya untuk ini adalah membungkus semuanya dalam blok coba-tangkap besar di mana ada beberapa janji yang dibuat dapat membuatnya rumit untuk menangani kesalahan secara khusus pada janji yang membuatnya. Dengan alternatif menjadi beberapa blok coba-tangkap yang menurut saya sama-sama tidak praktis

Tony
sumber
8

Alternatif yang lebih bersih adalah sebagai berikut:

Karena fakta bahwa setiap fungsi asinkron secara teknis adalah sebuah janji

Anda dapat menambahkan tangkapan ke fungsi saat memanggilnya dengan await

async function a(){
    let error;

    // log the error on the parent
    await b().catch((err)=>console.log('b.failed'))

    // change an error variable
    await c().catch((err)=>{error=true; console.log(err)})

    // return whatever you want
    return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))

Tidak perlu try catch, karena semua error promise ditangani, dan Anda tidak memiliki error kode, Anda dapat mengabaikannya di induk !!

Katakanlah Anda bekerja dengan mongodb, jika ada kesalahan Anda mungkin lebih suka menanganinya dalam fungsi yang memanggilnya daripada membuat pembungkus, atau menggunakan coba tangkap.

semangat
sumber
Anda memiliki 3 fungsi. Satu mendapatkan nilai dan menangkap kesalahan, yang lain Anda kembalikan jika tidak ada kesalahan dan akhirnya panggilan ke fungsi pertama dengan callback untuk memeriksa apakah yang mengembalikan kesalahan. Semua ini diselesaikan dengan satu "janji". Lalu (cb) .catch (cb) atau blok trycatch.
Kepala koshi
@Chiefkoshi Seperti yang Anda lihat, satu tangkapan tidak akan berfungsi karena kesalahan ditangani secara berbeda dalam ketiga kasus. Jika yang pertama gagal itu mengembalikan d (), jika yang kedua gagal itu mengembalikan nol jika yang terakhir gagal, pesan kesalahan yang berbeda ditampilkan. Pertanyaan menanyakan penanganan kesalahan saat menggunakan await. Jadi itu jawabannya juga. Semua harus dijalankan jika ada yang gagal. Mencoba menangkap blok akan membutuhkan tiga dari mereka dalam contoh khusus ini yang tidak lebih bersih
zardilior
1
Pertanyaannya tidak meminta eksekusi setelah janji yang gagal. Di sini Anda menunggu B, lalu jalankan C dan kembalikan D jika error. Bagaimana pembersih ini? C harus menunggu B namun mereka tidak bergantung satu sama lain. Saya tidak melihat alasan mengapa mereka bersama-sama berada di A jika mereka mandiri. Jika mereka bergantung satu sama lain, Anda ingin menghentikan eksekusi C jika B gagal, pekerjaan .then.catch atau try-catch. Saya berasumsi mereka tidak mengembalikan apa-apa dan melakukan beberapa tindakan asinkron yang sama sekali tidak terkait dengan A. Mengapa mereka dipanggil dengan async await?
Kepala koshi
Pertanyaannya adalah mengenai alternatif untuk mencoba menangkap blok untuk menangani kesalahan saat menggunakan async / await. Contoh di sini harus deskriptif dan tidak lain adalah contoh. Ini menunjukkan penanganan individu atas operasi independen secara berurutan yang biasanya menggunakan async / await. Mengapa mereka dipanggil dengan async await, hanya untuk menunjukkan bagaimana hal itu dapat ditangani. Ini deskriptif lebih dari dibenarkan.
zardilior
2

Saya ingin melakukan dengan cara ini :)

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});

Ini mirip dengan menangani kesalahan dengan co

const test = opts => {
  return co(function*() {

    // do sth
    yield sthError();
    return 'ok';

  }).catch(err => {
    console.error(err);
  });
};
Cooper H incred
sumber
Kode tidak terlalu jelas, terlihat menarik, bisakah Anda mengedit?
zardilior
Sangat disayangkan bahwa tidak ada penjelasan dalam jawaban ini karena ini benar-benar menunjukkan cara yang bagus untuk menghindari mencoba menangkap setiap const yang Anda tetapkan await!
Jim
0

catchMenurut pengalaman saya, cara ini berbahaya. Setiap kesalahan yang terjadi di seluruh tumpukan akan ditangkap, bukan hanya kesalahan dari janji ini (yang mungkin bukan yang Anda inginkan).

Argumen kedua untuk suatu janji sudah merupakan panggilan balik penolakan / kegagalan. Lebih baik dan lebih aman untuk menggunakannya.

Berikut ini skrip ketikan satu baris yang saya tulis untuk menangani ini:

function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
  return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}

// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
  apiClient.getCurrentUser()
);
sarink
sumber