【问题标题】:Writing Clean Code With Nested Promises使用嵌套的 Promise 编写干净的代码
【发布时间】:2013-04-01 13:29:53
【问题描述】:

我正在编写一个与 Apple 对话以验证收据的应用。他们有一个沙盒和生产 URL,你可以发布到。

在与 Apple 通信时,如果您收到 21007 状态,则表示您正在发布到生产 url,而此时您应该发布到沙盒一。

所以我写了一些代码来方便重试逻辑。这是我的代码的简化版本:

var request = require('request')
  , Q = require('q')
  ;

var postToService = function(data, url) {
  var deferred = Q.defer();
  var options = {
    data: data,
    url: url
  };

  request.post(options, function(err, response, body) {
    if (err) { 
      deferred.reject(err);
    } else if (hasErrors(response)) {
      deferred.reject(response);
    } else {
      deferred.resolve(body);
    }
  });

  return deferred.promise;
};

exports.verify = function(data) {
  var deferred = Q.defer();

  postToService(data, "https://production-url.com")
    .then(function(body) {
      deferred.resolve(body);
    })
    .fail(function(err) {
      if (err.code === 21007) {
        postToService(data, "https://sandbox-url.com")
          .then(function(body){
            deferred.resolve(body);
          })
          .fail(function(err) {
            deferred.reject(err);
          });
      } else {
        deferred.reject(err);
      }

    });

  return deferred.promise;
};

验证函数中的重试部分非常难看,嵌套的 Promise 很难阅读。有更好的方法吗?

【问题讨论】:

  • 想到的一个想法是使用 Iced CoffeeScript,它具有对连续传递的语法支持(类似于 C# 的 async/await):maxtaco.github.io/coffee-script。不幸的是,这需要使用 CoffeeScript,以及 grunt 等不支持的“非标准”变体。

标签: javascript node.js promise


【解决方案1】:

您可能会考虑以下内容。我认为明智地使用空格可以提高可读性。您可能希望找到一个合理的风格标准,让您的团队感觉良好并坚持下去!

exports.verify = function(data) {
  var deferred = Q.defer();

  postToService(data, "https://production-url.com")

    .then(deferred.resolve, function(err) {

      if (err.code === 21007) {

        postToService(data, "https://sandbox-url.com")

          .then(deferred.resolve, deferred.reject);

      } else { deferred.reject(err); }

    });

 return deferred.promise;
};

【讨论】:

    【解决方案2】:

    这里有几种可能性。因为这个问题带有个人品味,所以你可能喜欢也可能不喜欢你所看到的!

    (录取 - 我没有测试过这段代码)

    选项 1 - 为 resolvereject 使用包装器。这会以辅助函数的形式添加“噪音”,但会整理其余部分。

    var resolve = function (deferred, ob) {
      return function () {
        deferred.resolve(ob);
      };
    };
    
    var reject = function (deferred, ob) {
      return function () {
        deferred.reject(ob);
      };
    };
    
    exports.verify = function(data) {
      var deferred = Q.defer();
    
      postToService(data, "https://production-url.com")
        .then(resolve(deferred, body))
        .fail(function(err) {
          if (err.code === 21007) {
            postToService(data, "https://sandbox-url.com")
              .then(resolve(deferred, body))
              .fail(reject(deferred, err));
          } else {
            deferred.reject(err);
          }
        });
    
      return deferred.promise;
    };
    

    选项 2 - 使用绑定。这具有使用现有 JS 功能的优势,但在创建回调时您对 deferred 有重复引用。

    exports.verify = function(data) {
      var deferred = Q.defer();
    
      postToService(data, "https://production-url.com")
        .then(deferred.resolve.bind(deferred, body))
        .fail(function(err) {
          if (err.code === 21007) {
            postToService(data, "https://sandbox-url.com")
              .then(deferred.resolve.bind(deferred, body))
              .fail(deferred.reject.bind(deferred, err));
          } else {
            deferred.reject(err);
          }
        });
    
      return deferred.promise;
    };
    

    选项 3 - 使用绑定和“方法句柄”(#2 的细微变化)。

    exports.verify = function(data) {
      var deferred = Q.defer();
      var resolve = deferred.resolve;
      var reject = deferred.reject;
    
      postToService(data, "https://production-url.com")
        .then(resolve.bind(deferred, body))
        .fail(function(err) {
          if (err.code === 21007) {
            postToService(data, "https://sandbox-url.com")
              .then(resolve.bind(deferred, body))
              .fail(reject.bind(deferred, err));
          } else {
            deferred.reject(err);
          }
        });
    
      return deferred.promise;
    };
    

    选项 4 - 推迟猴子补丁。

    function patch(deferred) {
      deferred.resolveFn = function (ob) {
        return function () {
          deferred.resolve(ob);
        };
      };
      deferred.rejectFn = function (ob) {
        return function () {
          deferred.reject(ob);
        };
      };
      return deferred;
    }
    
    exports.verify = function(data) {
      var deferred = patch(Q.defer());
    
      postToService(data, "https://production-url.com")
        .then(deferred.resolveFn(body))
        .fail(function(err) {
          if (err.code === 21007) {
            postToService(data, "https://sandbox-url.com")
              .then(deferred.resolveFn(body))
              .fail(deferred.rejectFn(err));
          } else {
            deferred.reject(err);
          }
        });
    
      return deferred.promise;
    };
    

    【讨论】:

      【解决方案3】:

      您可以在拒绝处理程序中重新抛出错误以继续拒绝承诺,或者您可以返回新的承诺来替换拒绝。

      exports.verify = function(data) {
        return postToService(data, "https://production-url.com")
          .fail(function(err) {
            if (err.code === 21007) {
              return postToService(data, "https://sandbox-url.com")
            } else {
              throw err
            }
          });
      };
      

      【讨论】:

      • 我喜欢这个。不错,干净,简单!
      【解决方案4】:

      Stuart 的回答是对的,关键是要连锁承诺。我想澄清一下,不需要使用 Q.defer 来包装。它甚至被认为是一种反模式。在这里查看原因The Deferred anti-pattern

      var request = require('request')
          , Q = require('q');
      
      var PRODUCTION_URL = "https://production-url.com",
      var SANDBOX_URL    = "https://sandbox-url.com",
      
      
      export.verify = function() {
      
        return postToProduction(data)
               .fail( function(error) {
                   if (error.code === 21007 ) return postToSanbox(data);
                   throw error;
               });
      }
      
      function postToProduction(data) {
          return postToService(data, PRODUCTION_URL);
      }
      
      function postToSandbox(data) {
          return postToService(data, SANDBOX_URL);
      }
      
      function postToService(data, url) {
         var deferred = Q.defer();
      
         var options = {
            data: data,
            url: url
         };
      
        request.post(options, function(err, response, body) {
          if (err) return deferred.reject(err);
          if (hasErrors(response)) return deferred.reject(response);
      
          deferred.resolve(body);    
        });
      
        return deferred.promise;   
      }
      

      【讨论】:

      • 你可以通过使用Q.ninvoke(和配偶)完全避免延期
      猜你喜欢
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-27
      • 1970-01-01
      • 2016-08-23
      • 2010-10-28
      • 1970-01-01
      相关资源
      最近更新 更多