Написание чистого кода с вложенными промисами

Я пишу приложение, которое общается с 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;
};

Часть повторных попыток в функции проверки довольно уродлива и трудна для чтения с вложенными промисами. Есть ли лучший способ сделать это?


person Anton    schedule 09.04.2013    source источник
comment
Одна идея, которая приходит на ум, — использовать Iced CoffeeScript, который имеет синтаксическую поддержку передачи продолжения (аналогично async/await C#): maxtaco.github.io/coffee-script. К сожалению, это потребует использования CoffeeScript и его нестандартного варианта, который не поддерживается grunt и т.п.   -  person millimoose    schedule 10.04.2013


Ответы (4)


Вы можете повторно сгенерировать ошибку в обработчике отклонения, чтобы продолжить отклонение обещания, или вы можете вернуть новое обещание, чтобы заменить отклонение.

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
      }
    });
};
person Stuart K    schedule 10.04.2013
comment
Мне это нравится. Красиво, чисто и просто! - person Anton; 10.04.2013

Вот несколько возможностей. Поскольку в этом вопросе есть элемент личного вкуса, вам может понравиться или не понравиться то, что вы видите!

(Признание — я не тестировал этот код)

Вариант 1. Используйте оболочку для resolve и reject. Это добавляет «шум» в виде вспомогательных функций, но убирает все остальное.

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 - Патч Monkey отложен.

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;
};
person Paul Grime    schedule 09.04.2013

Вы можете рассмотреть что-то вроде следующего. Я думаю, что разумное использование пробелов может улучшить читабельность. Вы, вероятно, захотите найти разумный стандарт стиля, который понравится вашей команде, и придерживаться его!

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;
};
person Andbdrew    schedule 09.04.2013

Ответ Стюарта правильный, дело было в том, чтобы связать обещания. и я хотел бы уточнить, что не нужно использовать Q.defer только для упаковки. это даже считается анти-шаблоном. См. причины здесь >

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;   
}
person yeraycaballero    schedule 07.04.2015
comment
Вы можете полностью избежать отсрочек, используя Q.ninvoke (и консорты) - person Bergi; 07.04.2015