7

単純な約束を超えるものは、たいてい当惑します。この場合、N 個のオブジェクトに対して連続して 2 つの非同期呼び出しを行う必要があります。まず、ディスクからファイルをロードし、そのファイルをメール サーバーにアップロードする必要があります。私は 2 つのアクションを一緒に実行することを好みますが、すべての読み取りを最初に実行し、次にすべてのアップロードを実行することで機能するようになりました。以下のコードは機能しますが、もっとうまくできると思わずにはいられません。私が理解できないことの 1 つは、なぜ when.all が拒否しないのかということです。ドキュメントの私の解釈は、約束の1つが拒​​否された場合、.allが拒否されることを暗示しているようです。エラーをテストするために、下位の解決をコメントアウトしました。エラーがなければ、問題なく動作し、理にかなっているように見えます。

mail_sendOne({
  from: 'greg@', 
  to: 'wilma@', 
  subject: 'F&B data', 
  attachments: [
    {name: 'fred.html', path: '/fred.html'}, 
    {name: 'barney.html', path: '/barney.html'}
  ]
})
.done(
  function(res) {
    console.log(res)
  },
  function(err) {
    console.log('error ', err);
  }
)  

function mail_sendOne(kwargs) {
  var d = when.defer();
  var promises = [], uploadIDs = [], errs = [];

  // loop through each attachment
  for (var f=0,att; f < kwargs.attachments.length; f++) {
    att = kwargs.attachments[f];

    // read the attachment from disk  
    promises.push(readFile(att.path)
    .then(
      function(content) {
        // upload attachment to mail server
        return uploadAttachment({file: att.name, content: content})
        .then(
          function(id) {
            // get back file ID from mail server
            uploadIDs.push(id)
          },
          function(err) {
            errs.push(err)
          }
        )
      },
      function(err) {
        errs.push(err)
      }
    ))
  }

  // why doesn't this reject?
  when.all(promises)
  .then(
    function(res) {
      if (errs.length == 0) {
        kwargs.attachments = uploadIDs.join(';');
        sendEmail(kwargs)
        .done(
          function(res) {
            d.resolve(res);
          },
          function(err) {
            d.reject(err);
          }      
        )
      }
      else {
        d.reject(errs.join(','))
      }
    }
  )

  return d.promise;
}

function readFile(path) {
  var d = when.defer();
  var files = {
    '/fred.html': 'Fred Content',
    '/barney.html': 'Barney Content'
  }

  setTimeout(function() {
    d.reject('Read error');
    //d.resolve(files[path]);
  }, 10);

  return d.promise;
}

function uploadAttachment(obj) {
  var d = when.defer();

  setTimeout(function() {
    d.reject('Upload error');
    //d.resolve(new Date().valueOf());
  }, 10);

  return d.promise;
}

function sendEmail(kwargs) {
  var d = when.defer();

  setTimeout(function(){
    console.log('sending ', kwargs)
  }, 5);

  return d.promise;
}
4

1 に答える 1

9

コードを必要以上に乱雑にするアンチパターンがいくつかあります。1 つは、それらをチェーンする必要がある場合.thenに、コールバック内で使用することです。.thenもう 1 つは、遅延アンチパターンです。

まず、読み取りとアップロード用にそれぞれ 1 つの関数を作成しましょう。どちらもそれぞれのエラーを処理し、さらにコンテキストを使用して新しいエラーをスローします。

function readAndHandle(att) {
    return readFile(att.path)
        .catch(function (error) {
            throw new Error("Error encountered when reading " + att.path + error);
        });
}

function uploadAndHandle(att, content) {
    return uploadAttachment({file: att.name, content: content})
        .catch(function (error) {
            throw new Error("Error encountered when uploading " + att.path + error);
        });
}

次に、これら 2 つを組み合わせて、最初にファイルを読み取り、次にアップロードする関数にしましょう。この関数は promise を返します。

// returns a promise for an uploaded file ID
function readAndUpload(att) {
    return readAndHandle(att)
        .then(function (content) {
             return uploadAndHandle(att, content);
        });
}

.map()これで、添付ファイルの配列をファイル ID の promise の配列にマップするために使用できます。

var uploadedIdsPromise = kwargs.attachments.map(readAndUploadAsync);

そして、それが に渡すことができるものですwhen.all()。この.thenハンドラーは、ID の配列をそのコールバックに渡します。

return when.all(uploadedIdsPromise)
    .then(function (ids) {
        kwargs.attachments = ids.join(";");
        return sendEmail(kwargs);
    })
    .catch(function (error) {
        // handle error
    });

それが大まかな要点です。

ここで注意すべき重要な点の 1 つは、変数を変更する 1 つの場所を除いてkwargs、promise は promise チェーンの外部にあるものを変更していないということです。これは、ロジックをクリーンでモジュール化するのに役立ちます。

延期されたアンチパターン

上記のコードには もd.resolveも含まれていないことに注意してください。d.rejectsを使用する必要がdeferredあるのは、利用可能な promise がまだない場合 (またはその他のいくつかの特別な状況) だけです。それでも、最近ではプロミスを作成するための推奨される方法があります。

when.js API ドキュメントには次のように書かれています。

注: when.defer の使用は推奨されません。ほとんどの場合、 when.promise 、 when.try 、または when.lift を使用すると、問題をより適切に分離できます。

Promise 以外の非同期 API から Promise を作成する現在推奨される方法は、明らかにするコンストラクタ パターンを使用することです。uploadAttachment()関数を例に取ると、次のようになります。

function uploadAttachment(obj) {
  return when.promise(function (resolve, reject) {
      setTimeout(function() {
          resolve(new Date().valueOf());
          // or reject("Upload error");
      }, 10);
  });
}

これが ES6 promise API の仕組みであり、deferredオブジェクトをシャッフルする必要がなくなります。

于 2014-12-19T16:41:48.217 に答える