Обещания: выполнить что-то независимо от разрешения/отклонения?

Используя шаблон проектирования Promises, можно ли реализовать следующее:

 var a, promise

 if promise.resolve
     a = promise.responsevalue;

 if promise.reject
     a = "failed"

 AFTER resolution/rejection. Not ASYNC!!
     send a somewhere, but not asynchronously. //Not a promise

То, что я ищу, это что-то вроде finally в ситуации try - catch.

PS: я использую полифил ES6 Promise на NodeJS.


person nikjohn    schedule 08.08.2016    source источник
comment
Это чистый ES6? См. здесь: stackoverflow.com/questions/38575561/   -  person Zach    schedule 08.08.2016
comment
Я использую полифил ES6 Promise на NodeJS.   -  person nikjohn    schedule 08.08.2016
comment
Возможный дубликат обратного вызова с установленным обещанием ES6?   -  person Zach    schedule 08.08.2016
comment
Вы не можете проверить статус или результат такого промиса. Как только вы получите объект обещания, вы можете вызвать только его метод then или catch и предоставить соответствующие обратные вызовы, которые будут вызываться всякий раз, когда позже ваше обещание разрешается или отклоняется.   -  person Redu    schedule 08.08.2016


Ответы (2)


ПРИМЕЧАНИЕ. finally теперь является стандартной частью обещаний JavaScript, поэтому вы должны сделать следующее:

thePromise.then(result => doSomething(result)
          .catch(error => handleOrReportError(error))
          .finally(() => doSomethingAfterFulfillmentOrRejection());

Ответ до finally был стандартным:


Если вы возвращаете значение из catch, вы можете просто использовать then в результате catch.

thePromise.then(result => doSomething(result)
          .catch(error => handleErrorAndReturnSomething(error))
          .then(resultOrReturnFromCatch => /* ... */);

... но это означает, что вы преобразуете отказ в выполнение (возвращая что-то из catch, а не выбрасывая или возвращая отклоненное обещание), и полагаетесь на этот факт.


Если вам нужно что-то, что прозрачно передает выполнение/отклонение без его изменения, в обещаниях ES2015 ("ES6") нет ничего, что делало бы это (редактировать: опять же, теперь есть), но это легко написать (это в ES2015, но у меня перевод ES5 ниже):

{
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    Object.defineProperty(Promise.prototype, "finally", {
        value(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    });
}

Пример:

{
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  Object.defineProperty(Promise.prototype, "finally", {
    value(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  });
}
test("p1", Promise.resolve("good")).finally(
  () => {
    test("p2", Promise.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

Пара замечаний по этому поводу:

  1. Обратите внимание на использование this.constructor, так что мы вызываем resolve для любого типа обещания (включая возможный подкласс), созданного исходным обещанием; это согласуется с тем, как Promise.resolve и другие работают и являются важной частью поддержки подклассов обещаний.

  2. Вышеприведенное намеренно не включает какой-либо аргумент обратного вызова finally и не указывает, было ли обещание выполнено или отклонено, чтобы соответствовать finally в классической структуре try-catch-finally. Но при желании можно было бы легко передать часть этой информации обратному вызову.

  3. Точно так же в приведенном выше примере не используется значение, возвращаемое обратным вызовом finally, за исключением того, что, если это промис, он ожидает выполнения промиса, прежде чем продолжить цепочку.

Вот перевод ES5 этого:

(function() {
    function worker(ctor, f, done) {
        return ctor.resolve(f()).then(done, done);
    }
    Object.defineProperty(Promise.prototype, "finally", {
        value: function(f) {
            var ctor = this.constructor;
            return this.then(
                function(result) {
                    return worker(ctor, f, function() {
                        return result;
                    });
                },
                function(error) {
                    return worker(ctor, f, function() {
                        throw error;
                    });
                }
            );
        }
    });
})();

Пример:

(function() {
  function worker(ctor, f, done) {
    return ctor.resolve(f()).then(done, done);
  }
  Object.defineProperty(Promise.prototype, "finally", {
    value: function(f) {
      var ctor = this.constructor;
      return this.then(
        function(result) {
          return worker(ctor, f, function() {
            return result;
          });
        },
        function(error) {
          return worker(ctor, f, function() {
            throw error;
          });
        }
      );
    }
  });
})();

test("p1", Promise.resolve("good")).finally(function() {
  test("p2", Promise.reject("bad"));
});

function test(name, p) {
  return p.then(
      function(result) {
        console.log(name, "initial fulfillment:", result);
        return result;
      },
      function(error) {
        console.log(name, "initial rejection; propagating it");
        throw error;
      }
    )
    .finally(function() {
      console.log(name, "in finally");
    })
    .then(
      function(result) {
        console.log(name, "fulfilled:", result);
      },
      function(error) {
        console.log(name, "rejected:", error);
      }
    );
}

Я думаю, что это самый простой способ интегрировать эту функциональность в полифилл Promise в ES5.


Или, если вы предпочитаете создать подкласс Promise, а не изменять его прототип:

let PromiseX = (() => {
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    class PromiseX extends Promise {
        finally(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    }
    PromiseX.resolve = Promise.resolve;
    PromiseX.reject = Promise.reject;

    return PromiseX;
})();

Пример:

let PromiseX = (() => {
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  class PromiseX extends Promise {
    finally(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  }
  PromiseX.resolve = Promise.resolve;
  PromiseX.reject = Promise.reject;

  return PromiseX;
})();

test("p1", PromiseX.resolve("good")).finally(
  () => {
    test("p2", PromiseX.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}


Вы сказали, что хотите сделать это без расширения подклассов Promise.prototype или. В ES5 служебную функцию было бы чрезвычайно неудобно использовать, потому что вам нужно было бы передать ей промис для выполнения действия, что совершенно не соответствовало бы обычному использованию промиса. В ES2015 можно сделать что-то более естественное, но вызвать его все равно сложнее, чем изменить прототип или создать подкласс:

let always = (() => {
    let worker = (f, done) => {
        return Promise.resolve(f()).then(done, done);
    };
    return function always(f) {
        return [
            result => worker(f, () => result),
            error  => worker(f, () => { throw error; })
        ];
    }
})();

Применение:

thePromise.then(...always(/*..your function..*/)).

Обратите внимание на использование оператора распространения (поэтому он не будет работать в ES5), поэтому always может передавать оба аргумента then.

Пример:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", Promise.resolve("good")).then(...always(
  () => {
    test("p2", Promise.reject("bad"));
  }
));
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}


В комментариях вы выразили опасение, что finally не дождутся обещанного; вот этот последний пример always снова, с задержками, чтобы продемонстрировать, что это так:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", 500, false, "good").then(...always(
  () => {
    test("p2", 500, true, "bad");
  }
));

function test(name, delay, fail, value) {
  // Make our test promise
  let p = new Promise((resolve, reject) => {
    console.log(name, `created with ${delay}ms delay before settling`);
    setTimeout(() => {
      if (fail) {
        console.log(name, "rejecting");
        reject(value);
      } else {
        console.log(name, "fulfilling");
        resolve(value);
      }
    }, delay);
  });

  // Use it
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

person T.J. Crowder    schedule 08.08.2016
comment
@tj Разве это не будет выполняться resultOrReturnFromCatch асинхронно? Например, если есть 5-секундная задержка в исходном разрешении обещания, не будет ли resultOrReturnFromCatch просто продолжать и выполняться в любом случае? - person nikjohn; 08.08.2016
comment
@ChanandlerBong: Нет, с чего бы это? Кстати, я добавил версию служебной функции в конец, поэтому вам не нужно расширять прототипы подкласса. - person T.J. Crowder; 08.08.2016
comment
@ChanandlerBong: Мне пришло в голову, что вы сказали, что используете полифилл Promise и хотели бы использовать ES6, предполагая, что вы не используете его сейчас. Таким образом, все эти ответы со стрелочными функциями (и оператором распространения в моем случае) имеют ... ограниченную полезность. :-) Я добавил версию расширения прототипа ES5 выше. Я думаю, что это самый разумный способ приблизиться к этому в ES5. Вспомогательную функцию always практически невозможно разумно написать в ES5 из-за необходимости передавать оба аргумента функции then (отсюда и оператор распространения). - person T.J. Crowder; 08.08.2016

Код ES2015:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));
person Maxx    schedule 08.08.2016
comment
promise.then(val => val) бесполезно. - person Ry-♦; 08.08.2016
comment
@Ryan это просто для примера на этот вопрос - person Maxx; 08.08.2016
comment
Разве это не будет выполняться doSomethignWithA(a) асинхронно? Например, если есть 5-секундная задержка в исходном разрешении обещания, не будет ли doSomethingWithA(a) просто продолжать и выполняться в любом случае? - person nikjohn; 08.08.2016
comment
@ChanandlerBong, если я правильно понимаю вопрос, то да, doSomethingWithA будет работать только после оригинального promise. - person Maxx; 08.08.2016
comment
Я попробовал и добавил 5-секундную задержку. Он все равно просто выполняет doSomethingWithA. Так это асинхронно. Я ищу решение для синхронизации - person nikjohn; 08.08.2016
comment
@ChanandlerBong, я не могу ответить на ваш вопрос без примера кода. Возможно, ваш код задержки работает не так, как вы думаете. - person Maxx; 08.08.2016
comment
@ChanandlerBong: Вышеприведенное не будет работать doSomethingWithA до тех пор, пока обещание .then(a => doSomethingWithA(a)) не будет вызвано при расчетах. - person T.J. Crowder; 08.08.2016