Bagaimana cara menolak sintaks async / wait?

282

Bagaimana saya bisa menolak janji yang dikembalikan oleh fungsi async / menunggu?

misalnya asalnya

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Terjemahkan ke async / tunggu

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

Jadi, bagaimana saya bisa menolak janji ini dalam kasus ini?

Phoenix
sumber
20
Hindari Promiseantipattern konstruktor ! Bahkan cuplikan pertama seharusnya ditulisfoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi
10
Saya pikir akan sangat membantu untuk menerjemahkan kode dalam pertanyaan ini ke dalam vanilla JS, karena pertanyaannya tidak ada hubungannya dengan TypeScript. Jika saya melakukannya, apakah suntingan itu akan diterima?
Jacob Ford

Jawaban:

328

Anda bertaruh terbaik adalah untuk throwsebuah Errorpembungkus nilai, yang menghasilkan janji menolak dengan Errorpembungkus nilai:

} catch (error) {
    throw new Error(400);
}

Anda juga bisa hanya thrownilainya, tetapi kemudian tidak ada informasi jejak tumpukan:

} catch (error) {
    throw 400;
}

Bergantian, kembalikan janji yang ditolak dengan nilai Errorpembungkus, tetapi itu tidak idiomatis:

} catch (error) {
    return Promise.reject(new Error(400));
}

(Atau hanya return Promise.reject(400);, tapi sekali lagi, maka tidak ada informasi konteks.)

(Dalam kasus Anda, saat Anda menggunakan TypeScriptdan foonilainya kembali Promise<A>, Anda akan menggunakan return Promise.reject<A>(400 /*or error*/);)

Dalam suatu async/ awaitsituasi, yang terakhir itu mungkin sedikit salah pertandingan semantik, tetapi itu berhasil.

Jika Anda melempar Error, itu cocok dengan apa pun yang mengonsumsi foohasil Anda dengan awaitsintaks:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}
TJ Crowder
sumber
12
Dan karena async / tunggu adalah tentang mengambil aliran async kembali ke sinkronisasi sintaks, throwlebih baik daripada Promise.reject()IMO. Apakah akan throw 400merupakan pertanyaan yang berbeda. Dalam OP itu menolak 400, dan kita bisa berargumen bahwa itu harus menolak Errorsebaliknya.
unional
2
Ya, bagaimanapun, jika rantai kode Anda benar-benar menggunakan async / menunggu, maka Anda akan ..... sulit mengetik di sini, izinkan saya demo sebagai jawaban
nasional
1
apakah ada alasan Anda ingin melempar kesalahan baru sebagai lawan dari kesalahan yang diberikan kepada Anda di blok tangkap?
Adrian M
1
@sebastian - Saya tidak tahu apa maksud Anda di sana. Dalam asyncfungsi, tidak ada resolveatau rejectfungsi. Ada returndan throw, yang merupakan cara idiomatis untuk menyelesaikan dan menolak asyncjanji fungsi.
TJ Crowder
1
@ Jan-PhilipGehrcke - Anda bisa , tapi saya tidak pernah melakukannya. Itu membuat contoh, newbuat itu eksplisit. Perhatikan juga bahwa Anda tidak dapat meninggalkannya jika memiliki Errorsubclass ( class MyError extends Error), jadi ...
TJ Crowder
146

Mungkin juga harus disebutkan bahwa Anda hanya dapat rantai catch()fungsi setelah panggilan operasi async Anda karena di bawah tenda masih ada janji yang dikembalikan.

await foo().catch(error => console.log(error));

Dengan cara ini Anda dapat menghindari try/catchsintaksis jika Anda tidak menyukainya.

David
sumber
1
Jadi jika saya ingin menolak asyncfungsi saya , saya membuang pengecualian dan kemudian menangkapnya dengan baik .catch()seperti jika saya kembali Promise.rejectatau menelepon reject. Saya suka itu!
icl7126
7
Saya tidak mengerti mengapa ini harus menjadi jawaban yang diterima. Tidak hanya pembersih jawaban yang diterima, tetapi juga menangani semua kemungkinan awaitkegagalan dalam satu rutin. Kecuali jika dibutuhkan kasus yang sangat spesifik untuk masing-masing, awaitsaya tidak mengerti mengapa Anda ingin menangkapnya seperti ini. Hanya saya pendapat yang rendah hati.
edgaralienfoe
1
@ jablesauce untuk use case saya, tidak hanya saya perlu menangkap setiap awaitkegagalan secara terpisah, tetapi saya juga perlu bekerja dengan kerangka kerja berbasis Janji yang menolak janji kesalahan.
Reuven Karasik
Itu tidak berhasil untuk saya. Tampaknya tidak akan memblokir jika url gagal. [response] = menunggu oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ('Tidak dapat mengambil izin repositori', err); callback (err);})
sn.anurag
1
tidak perlu awaitkata kunci di sini.
Ashish Rawat
12

Anda bisa membuat fungsi wrapper yang menerima janji dan mengembalikan array dengan data jika tidak ada kesalahan dan kesalahan jika ada kesalahan.

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

Gunakan seperti ini di ES7 dan dalam fungsi async :

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}
Andy
sumber
1
Sepertinya upaya untuk memiliki sintaks Go yang indah tetapi tanpa banyak keanggunan. Saya menemukan kode menggunakannya untuk dikaburkan hanya cukup untuk menyedot nilai dari solusi.
Kim
8

Cara yang lebih baik untuk menulis fungsi async adalah dengan mengembalikan Janji yang tertunda sejak awal dan kemudian menangani penolakan dan resolusi dalam panggilan balik janji tersebut, daripada hanya memuntahkan janji yang ditolak karena kesalahan. Contoh:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

Maka Anda hanya rantai metode pada janji yang dikembalikan:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Sumber - tutorial ini:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

OzzyTheGiant
sumber
5
Pertanyaan khusus yang diajukan tentang penggunaan async / menunggu. Tidak menggunakan janji
Mak
Jawaban ini tidak dimaksudkan sebagai jawaban yang benar dan pasti. Ini adalah jawaban dukungan untuk jawaban lain yang diberikan di atas. Saya akan meletakkannya sebagai komentar tetapi mengingat bahwa saya memiliki kode, bidang jawaban adalah tempat yang lebih baik.
OzzyTheGiant
Terima kasih telah mengklarifikasi. Memperlihatkan cara membuat fungsi async jelas sangat membantu. Memperbarui blok kode kedua untuk menunggu akan jauh lebih relevan dan bermanfaat. Cheers
Mak
Saya telah mengedit respons Anda untuk memperbaruinya. Beri tahu saya jika saya melewatkan sesuatu
Mak
4

Saya punya saran untuk menangani dengan benar penolakan dalam pendekatan baru, tanpa memiliki beberapa blok uji coba.

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

Di mana fungsi to.ts harus diimpor dari:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

Kredit masuk ke Dima Grossman di tautan berikut .

Pedro Lourenço
sumber
1
Saya menggunakan konstruksi ini hampir secara eksklusif (jauh lebih bersih) dan ada modul 'to' yang telah ada untuk sementara waktu npmjs.com/package/await-to-js . Tidak perlu deklarasi terpisah, cukup taruh di depan tugas yang didekonstruksi. Juga bisa dilakukan hanya let [err]=jika hanya memeriksa kesalahan.
DKebler
3

Ini bukan jawaban atas @TJ Crowder. Hanya komentar menanggapi komentar "Dan sebenarnya, jika pengecualian akan dikonversi menjadi penolakan, saya tidak yakin apakah saya benar-benar terganggu jika itu adalah Kesalahan. Alasan saya untuk melemparkan hanya Kesalahan mungkin tidak berlaku. "

jika kode Anda menggunakan async/ await, maka masih merupakan praktik yang baik untuk menolak dengan Erroralih - alih 400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}
bersatu
sumber
3

Saya tahu ini adalah pertanyaan lama, tapi saya baru saja menemukan benang merah dan tampaknya ada perselisihan di sini antara kesalahan dan penolakan yang berjalan bertentangan (dalam banyak kasus, setidaknya) dari saran yang sering diulangi untuk tidak menggunakan penanganan pengecualian untuk menangani kasus yang diantisipasi. Sebagai ilustrasi: jika metode async mencoba untuk mengotentikasi pengguna dan otentikasi gagal, itu adalah penolakan (salah satu dari dua kasus yang diantisipasi) dan bukan kesalahan (misalnya, jika API otentikasi tidak tersedia.)

Untuk memastikan saya tidak hanya membelah rambut, saya menjalankan tes kinerja tiga pendekatan berbeda untuk itu, menggunakan kode ini:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Beberapa hal yang ada di sana dimasukkan karena ketidakpastian saya tentang penerjemah Javascript (saya hanya ingin turun satu lubang kelinci pada suatu waktu); misalnya, saya memasukkan doSomethingfungsi dan mengembalikannya dummyValueuntuk memastikan bahwa blok bersyarat tidak akan dioptimalkan.

Hasil saya adalah:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

Saya tahu bahwa ada banyak kasus di mana tidak ada masalah dengan memburu optimasi kecil, tetapi dalam sistem skala yang lebih besar hal-hal ini dapat membuat perbedaan kumulatif yang besar, dan itu perbandingan yang sangat mencolok.

SO ... sementara saya pikir pendekatan jawaban yang diterima adalah suara dalam kasus di mana Anda berharap harus menangani kesalahan tak terduga dalam fungsi async, dalam kasus di mana penolakan hanya berarti "Anda harus pergi dengan Plan B (atau C, atau D ...) "Saya pikir preferensi saya adalah menolak menggunakan objek respons khusus.

RiqueW
sumber
2
Juga, ingatlah bahwa Anda tidak perlu stres tentang penanganan kesalahan yang tidak diantisipasi dalam fungsi async jika panggilan ke fungsi tersebut berada dalam blok coba / tangkap dalam cakupan terlampir karena - tidak seperti Janji - fungsi async menggelembungkan kesalahan mereka yang dilemparkan ke melampirkan lingkup, di mana mereka ditangani seperti kesalahan lokal ke lingkup itu. Itulah salah satu fasilitas utama async / tunggu!
RiqueW
Microbenchmark adalah iblis. Lihatlah lebih dekat angka-angkanya. Anda perlu melakukan sesuatu 1000x untuk melihat perbedaan 1 ms di sini. Ya, menambahkan lemparan / tangkap akan menonaktifkan fungsi. Tetapi a) jika Anda menunggu sesuatu yang async, kemungkinan untuk mengambil beberapa pesanan besar membutuhkan waktu lebih lama dari 0,0005 Ms terjadi di latar belakang. b) Anda perlu melakukannya 1000x untuk membuat perbedaan 1ms di sini.
Jamie Pate