0

さまざまな Deferred/promise ライブラリのドキュメントでは、1 つまたは 2 つの関数/メソッドを連鎖させて成功またはエラーを解決または拒否するなど、非常に単純なユース ケースを扱っていることがよくあります。

しかし、より複雑なユース ケース (5 つ以上の関数/メソッドのチェーン、ネストされた Deferred、他の Deferred 内から promise を返す) になると、私は手ぶらで来て、非常にイライラします。

たとえば、2 つのネストされた関数を含む関数があるとします。各子関数は promise を返し、親関数が子の成功/失敗の結果を返すようにします。

var saveUser = function(user) {
    var saveNewUser = function(user) {
        var deferred = when.defer();

        user.save(function (err) {
            if (err) {
                deferred.reject();
            }
            else {
                // forcing a rejection here for testing purposes
                deferred.reject(6);
            }
        });

        return deferred.promise;
    }

    var supplyUserCollections = function() {
        var deferred = when.defer();

        deferred.reject();

        return deferred.promise;
    }

    return saveNewUser(user).then(supplyUserCollections(), function() {
        console.log('failed to save new user');
    }).then(function(data) {
        console.log('succeeded to do everything');
    }, function() {
        console.log('failed to seed new user collections');
    });
}

これは機能しません。console.log奇妙なことに、両方の子関数に拒否/失敗を強制しているにもかかわらず、「すべての実行に成功しました」が発生します。a の最初のパラメーター.thenが成功例であるため、なぜこれが起こっているのか本当にわかりません。さらに、親関数saveUserが次のような別の広範な promise チェーンの一部であると仮定します。

dropExistingCollections(collections).then(saveEntities(albums), function() {
    console.log('failed to drop existing collections');
}).then(saveEntities(movies), function() {
    console.log('failed to save all albums');
}).then(saveEntities(games), function() {
    console.log('failed to save all movies');
}).then(saveUsers(users), function() {
    console.log('failed to save all games');    
}).then(function(data) {
    console.log(data);

    console.log('successfully saved all seed data');

    res.send('database data wiped and re-seeded');
}, function() {
    console.log('failed to save all users');
});

saveUser解決済み/拒否された Deferred を返すすべての単純な関数である、残りの関数と連鎖可能な方法でプロミスを適切に返す方法がよくわかりません。

Deferreds/promise のこれらのより複雑なユースケースのいくつかがどのように処理されることになっているのかについて、より明確にしたいと思います。これは明らかに非常に密度の高いトピックであり、私が見つけた資料のほとんどは、私にとって特にうまく共鳴するものではありません.

4

2 に答える 2

5

常に同期コードから始めます。はるかに理解しやすく、非同期コードへの変換はそれほど難しくありません。少し壊れた非同期コードではなく、同期コードをどのように記述するかを含めていれば、コードの意図を理解するのがずっと簡単だったでしょう。

簡潔な答え

あなたの最初の問題はおそらく、意図しないところに括弧を追加したことです。常に関数thenハンドラーに渡す必要があります。

return saveNewUser(user).then(supplyUserCollections /* NOTE: no parenthesis */, function() {
    console.log('failed to save new user');
}).then(function(data) {
    console.log('succeeded to do everything');
}, function() {
    console.log('failed to seed new user collections');
});

これは、2番目の機能についてもあなたの主な誤解のようです。

長い答え

書きたいコードが次のようになると想像してみてください (saveSyncメソッドとsaveユーザーのメソッドがある場合)。

function saveNewUser(user) {
  var result = user.saveSync();//may throw
  // forcing a throw here for testing purposes
  throw 6;
}

function supplyUserCollections() {
  throw new Error('failed supplyUserCollections');
}

function saveUser(user) {
  try {
    saveNewUser(user);
  } catch (ex) {
    console.log('failed to save new user');
    return;
  }
  try {
    supplyUserCollections();
  } catch (ex) {
    console.log('failed to seed new user collections');
    return;
  }
  console.log('succeeded to do everything');
}

saveNewUserこれで、supplyUserCollections非常に簡単に変換できます。

function saveNewUser(user) {
  var deferred = when.defer();

  user.save(function (err) {
    if (err) {
      deferred.reject();
    }
    else {
      // forcing a rejection here for testing purposes
      deferred.reject(6);
    }
  });

  return deferred.promise;
}

function supplyUserCollections() {
  var deferred = when.defer();

  deferred.reject();

  return deferred.promise;
}

これが完了したら、より複雑なsaveUserメソッドを変換する必要があります。

function saveUser(user) {
  saveNewUser(user)
    .then(function () {
      //this only happens if `saveNewUser` succeeded, like the bit after
      //a catch block containing a `return`
      return supplyUserCollections()
        .then(function () {
          console.log('succeeded to do everything');
        }, function (ex) {
          console.log('failed to seed new user collections');
        })
    }, function (ex) {
      //this happens when `saveNewUser` failed (just like the catch block)
      console.log('failed to save new user');
    })
}

今、同期関数のより可能性の高いバージョンは次のようなものかもしれないと私には思えます:

function saveUser(user) {
  saveNewUser(user);
  supplyUserCollections();
  console.log('succeeded to do everything');
}

次のように書くことができます:

function saveUser(user) {
  saveNewUser(user)
    .then(function () {
      //this only happens if `saveNewUser` succeeded
      return supplyUserCollections()
    })
    .then(function () {
      //this happens when both methods succeeded
      console.log('succeeded to do everything');
    })
}
于 2013-07-08T11:11:26.147 に答える
1

括弧の問題 (Forbes が指摘) を修正すると、最初の例は次のように出力されます。

failed to save new user
succeeded to do everything

ですから、最初のステップで失敗したのに、なぜすべての実行に成功したと表示されるのか疑問に思われるかもしれません。それは、あなたのフローで初期エラーを超えて、成功軌道に乗ったからです。エラー状態を維持するには、コールバックで同じエラーを再スローする必要があります。

saveNewUser(user).then(supplyUserCollections(), function(err) {
    console.log('failed to save new user');
    throw err; // Re-throw to maintain error state
})

これで、次のように表示されます。

failed to save new user
failed to seed new user collections

それでも、フローはベスト プラクティスに似ていません。まず、エラー メッセージの内容だけで、どのステップでフローがクラッシュしたかを明確に把握する必要があります。また、各ステップでエラーをリッスンするべきではありません。これは時代遅れであり、過度に冗長です。

Cannot save userエラーでクラッシュする可能性があり、Cannot seed new user collectionssaveNewUserでクラッシュする可能性があるとしましょう。フローは次のように単純でなければなりません。supplyUserCollections

saveNewUser(user).then(supplyUserCollections).done(function () {
    console.log("Success!");
}, function (err) {
    console.error(err.message);
});

次に、ステップのいずれかがクラッシュした場合、対応するメッセージが表示されます。これが、promise を効果的に操作する方法です。ほとんどの場合、チェーンの最後で一度だけエラーを処理する必要があります。

于 2013-07-09T12:45:16.437 に答える