Используйте cancel() внутри цепочки, созданной promisifyAll

Не уверен, что я достаточно ясен с этим заголовком, но предположим, что у меня есть класс Foo с method1, method2 и method3. Я обещаю его методы с помощью promisifyAll.

Затем у меня есть цепочка then, и я хочу отменить операцию в середине второй или первой, а затем не следует вызывать.

Я читал об аннулировании (http://bluebirdjs.com/docs/api/cancellation.html), но я не знаю, как его реализовать с помощью promisifyAll.

Код, который я получил, плюс то, что мне нужно:

var bluebird = require('bluebird');

function Foo() { }

Foo.prototype.method1 = function (cb) {};

Foo.prototype.method2 = function (cb) {};

Foo.prototype.method3 = function (cb) {};

var foo = bluebird.promisifyAll(new Foo());

foo.method1Async()
.then(function (r1) {
  // cancel then-chain
  res.json("Task stopped");
  // just stop right here
  promises.cancel();
})
.then(function (r2) {
  console.log(r2);
}).then(function (r3) {
  console.log(r3);
})
.catch(function (er) {
  console.log('Catch!');
});

Каков правильный способ получить этот результат? Я знаю, что могу просто выбросить что-то и поймать его в методе catch, но это внесет очень большой вклад в мой реальный код.

Ответ 1

Попробуйте что-то вроде этого:

var bluebird = require('bluebird');

function Foo() { }

Foo.prototype.method1 = function (cb) { cb(null, 'method1'); };
Foo.prototype.method2 = function (cb) { cb(null, 'method2'); };
Foo.prototype.method3 = function (cb) { cb(null, 'method3'); };

var foo = bluebird.promisifyAll(new Foo());

foo.method1Async()
.then(function (r1) {
  console.log('step 1');
  // cancel then-chain
  console.log("Task stopped");
  // just stop right here
  return bluebird.reject('some reason');
})
.then(function (r2) {
  console.log('step 2');
  console.log(r2);
}).then(function (r3) {
  console.log('step 3');
  console.log(r3);
})
.catch(function (er) {
  console.log('Catch!');
  console.log('Error:', er);
});

Вместо:

  return bluebird.reject('some reason');

вы можете использовать:

  throw 'some reason';

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

Обновление 1

Но если вы намерены последовательно запускать все 3 метода, вам также нужно будет вернуть следующее обещание на каждом шаге с помощью следующего:

var bluebird = require('bluebird');

function Foo() { }

Foo.prototype.method1 = function (cb) { cb(null, 'method1'); };
Foo.prototype.method2 = function (cb) { cb(null, 'method2'); };
Foo.prototype.method3 = function (cb) { cb(null, 'method3'); };

var foo = bluebird.promisifyAll(new Foo());

foo.method1Async()
.then(function (r1) {
  console.log('step 1');
  console.log('got value:', r1);
  // cancel? change to true:
  var cancel = false;
  if (cancel) {
    console.log("Task stopped");
    return bluebird.reject('some reason');
  } else {
    console.log('Keep going');
    return foo.method2Async();
  }
})
.then(function (r2) {
  console.log('step 2');
  console.log('got value:', r2);
  return foo.method3Async();
}).then(function (r3) {
  console.log('step 3');
  console.log('got value:', r3);
})
.catch(function (er) {
  console.log('Catch!');
  console.log('Error:', er);
});

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

Обновление 2

Другой пример, который не вызывает последний catch для этого случая:

foo.method1Async()
.then(function (r1) {
  console.log('step 1');
  console.log('got value:', r1);
  // cancel? change to true:
  var cancel = true;
  if (cancel) {
    console.log("Task stopped");
    return bluebird.reject('some reason');
  } else {
    console.log('Keep going');
    return foo.method2Async();
  }
})
.then(function (r2) {
  console.log('step 2');
  console.log('got value:', r2);
  return foo.method3Async();
}).then(function (r3) {
  console.log('step 3');
  console.log('got value:', r3);
})
.catch(function (er) {
  if (er === 'some reason') {
    return bluebird.resolve('ok');
  } else {
    return bluebird.reject(er);
  }
})
.catch(function (er) {
  console.log('Catch!');
  console.log('Error:', er);
});

Описание

Подумайте об этом так: в синхронном коде, если у вас есть:

r1 = fun1();
r2 = fun2();
r3 = fun3();

тогда единственный способ для fun1 отменить выполнение fun2 и fun3 - это выбросить исключение. Аналогично для promises единственный способ, которым один обработчик then мог отменить выполнение следующего обработчика then, - это вернуть отклоненное обещание. И точно так же, как с синхронным кодом, что исключенное исключение будет захвачено блоком catch, здесь отклоненное обещание будет передано обработчику catch.

С синхронным кодом у вас может быть внутренний try/catch, который улавливает исключение и повторяет его, если это не тот, который вы использовали для отмены выполнения. С помощью promises вы можете иметь более ранний обработчик catch, который делает практически то же самое.

Это то, что происходит с кодом в обновлении 2. Причина отклонения сравнивается с некоторым значением (которое в этом примере равно 'some reason'), и если оно равно, то возвращается обещанное обещание, и поэтому следующий catch обработчик не вызывается. Если это не равно, то причина отклонения возвращается снова как отклоненное обещание, которое затем передается следующему обработчику catch как "реальная" ошибка, которую вы хотите обработать последним обработчиком catch.