web-dev-qa-db-ja.com

約束:解決/拒否に関係なく何かを実行しますか?

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

私が探しているのは、try - catchの状況でのfinallyのようなものです。

PS:NodeJSでES6 Promiseポリフィルを使用しています

16
nikjohn

注:finallyはJavaScriptのpromiseの標準的な部分なので、次のようにします。

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")に組み込まれている約束はありません(edit:もう一度、今はありますが)書くのは簡単です(これは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 などの機能と一致しており、サブクラス化されたpromiseのサポートの重要な部分です。

  2. 上記は、意図的にfinallyコールバックへの引数を含まないであり、約束が順守されたか拒否されたかを示すものではありません。従来のtry-catch-finally構造のfinallyと一致するようにします。しかし、必要に応じて、その情報の一部をコールバックに簡単に渡すことができます。

  3. 同様に、上記はfinallyコールバックによって返された値を使用しませんexcept約束である場合、約束がチェーンを続行する前に解決します。

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);
      }
    );
}

これは、この機能をES5のPromiseポリフィルに統合する最も簡単な方法だと思います。


または、プロトタイプを変更するのではなく、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では、ユーティリティ関数はextremely使用するのが面倒です。これは、動作するという約束を渡す必要があるため、完全に機能しなくなるためです。通常のPromiseの使用方法のステップの。 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では機能しない理由です)。そのため、alwaysthenに両方の引数を提供できます。

例:

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);
    }
  );
}
16
T.J. Crowder

ES2015コード:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));
3
Maxx