8

node.js では、コールバック関数の最初の引数としてエラー メッセージを返すのが一般的です。純粋な JS (Promise、Step、seq など) でこの問題を解決する方法は多数ありますが、いずれも ICS と統合できるようには見えません。読みやすさをあまり損なわずにエラーを処理するための正しい解決策は何でしょうか?

例えば:

# makes code hard to read and encourage duplication
await socket.get 'image id', defer err, id
if err # ...
await Image.findById id, defer err, image
if err # ...
await check_permissions user, image, defer err, permitted
if err # ...


# will only handle the last error
await  
  socket.get 'image id', defer err, id
  Image.findById id, defer err, image
  check_permissions user, image, defer err, permitted

if err  # ...


# ugly, makes code more rigid
# no way to prevent execution of commands if the first one failed
await  
  socket.get 'image id', defer err1, id
  Image.findById id, defer err2, image
  check_permissions user, image, defer err3, permitted

if err1 || err2 || err3  # ...
4

2 に答える 2

12

私はこの問題をスタイルとコーディング規約で解決します。そして、それは常に出てきます。以下のスニペットを見てみましょう。実行可能な関数ができるように、もう少し具体化してください。

my_fn = (cb) ->
  await socket.get 'image id', defer err, id
  if err then return cb err, null
  await Image.findById id, defer err, image
  if err then return cb err, null
  await check_permissions user, image, defer err, permitted
  if err then return cb err, null
  cb err, image

あなたは正確に正しいです、これは醜いです、なぜならあなたは多くの場所でコードを短絡させており、あなたが戻るたびにcbを呼び出すことを忘れないでください。

提供した他のスニペットは、シリアル化が必要な場合に並列処理を導入するため、誤った結果をもたらします。

私の個人的なICSコーディング規約は次のとおりです。(1)関数から1回だけ戻る(コントロールは最後から外れます)。(2)すべて同じレベルのインデントでエラーを処理しようとします。私の好みのスタイルで、あなたが持っているものを書き直します:

my_fn = (cb) ->
  await socket.get 'image id', defer err, id 
  await Image.findById id, defer err, image                   unless err?
  await check_permissions user, image, defer err, permitted   unless err?
  cb err, image

socket.get呼び出しでエラーが発生した場合は、エラーを2回チェックする必要があり、明らかに両方の時間で失敗します。コードがすっきりするので、これが世界の終わりではないと思います。

または、次のようにすることもできます。

my_fn = (autocb) ->
  await socket.get 'image id', defer err, id
  if err then return [ err, null ]
  await Image.findById id, defer err, image
  if err then return [ err, null ]
  await check_permissions user, image, defer err, permitted
  return [ err, image ]

私のお気に入りのICS機能ではないautocbを使用している場合、関数を終了/短絡するたびに、コンパイラーがautocbを呼び出します。この構造は、経験からエラーが発生しやすいと思います。たとえば、関数の開始時にロックを取得する必要があり、今度はそれをn回解放する必要があると想像してください。他の人は同意しないかもしれません。

コメントで以下に指摘されているもう1つのメモ。 1つの値のみを受け入れるという点でautocb機能します。returnこの例のように複数の値を返したい場合は、配列またはディクショナリを返す必要があります。 deferここであなたを助けるために割り当てを破壊します:

await my_fn defer [err, image]
于 2013-01-14T20:25:39.860 に答える
4

IcedCoffeeScript リポジトリのIssue #35で説明されているように、iced-styleコネクタに基づく別の手法があります。これは、入力としてコールバック/遅延を受け取り、別のコールバック/遅延を返す関数です。

プロジェクトがコールバックへの引数の標準的な順序を持っていると想像してください。最初のパラメーターは常にエラーであり、成功すると null になります。また、エラーの最初の兆候で関数を終了したいとします。

最初のステップは、「ErrorShortCircuiter」または「ESC」と呼んでいるコネクタを作成することです。

{make_esc} = require 'iced-error'

次のように実装されています。

make_esc = (gcb, desc) -> (lcb) ->
    (err, args...) ->
        if not err? then lcb args...
        else if not gcb.__esc
            gcb.__esc = true
            log.error "In #{desc}: #{err}"
            gcb err

これが何をしているのかを見るために、それを使用する方法の例を考えてみましょう:

my_fn = (gcb) ->
    esc = make_esc gcb, "my_fn"
    await socket.get 'image id', esc defer id
    await Image.findById id, esc defer image
    await check_permissions user, image, esc defer permitted
    gcb null, image

このバージョンのmy_fnfirst は、ErrorShortCircuiter (またはesc) を作成します。その役割は次の 2 つgcbです。(2) エラーが発生した場所とエラーの内容に関するメッセージをログに記録します。明らかに、設定に基づいて正確な動作を変更する必要があります。次に、コールバックを使用したライブラリ関数への後続のすべての呼び出しには、通常どおり によって生成されたコールバックが与えられ、コネクタをdefer介して実行されます。これにより、コールバックの動作が変更されます。新しい動作は、エラー時にグローバル関数escを呼び出し、現在のブロックを成功時に終了させることです。また、成功の場合、null エラー オブジェクトを処理する必要がないため、後続のスロット ( 、、および など) のみが埋められます。gcbawaitidimagepermitted

この手法は非常に強力でカスタマイズ可能です。重要なアイデアは、 によって生成されるコールバックdeferは実際には継続であり、プログラム全体の後続の制御フローを変更できるということです。また、ライブラリでこれを行うことができるため、さまざまな規則でライブラリを呼び出すさまざまな種類のアプリケーションに必要なエラー動作を得ることができます。

于 2013-05-20T16:34:16.230 に答える