Promise.prototype.then()

then() メソッドは Promise を返します。最大 2 つの引数として、 Promise が成功した場合と失敗した場合のコールバック関数を取ります。

試してみましょう

Note: 片方または両方の引数が省略されたり、関数ではないものが渡されたりした場合、 then にはハンドラーが不足しますが、エラーは発生しません。 Promise が状態 (履行 (fulfillment) または拒否 (rejection)) を受け入れるに当たって then が呼び出された際に、 then がハンドラーを持たない場合は、 then が呼び出された元の Promise の最後の状態を受け入れた、追加のハンドラーのない新しい Promise が生成されます。

構文

p.then(onFulfilled[, onRejected]);

p.then(value => {
  // 履行
}, reason => {
  // 拒否
});

引数

onFulfilled 省略可

Promise が成功したときに呼び出される関数 (Function) です。この関数は 1 つの引数、 fulfillment value を持ちます。これが関数ではない場合は、内部的に "Identity" 関数 (受け取った引数を返す関数) に置き換えられます。

onRejected 省略可

Promise が拒否されたときに呼び出される関数 (Function) です。この関数は 1 つの引数、 rejection reason を持ちます。これが関数ではない場合は、内部的に "Thrower" 関数 (引数として受け取ったエラーを投げる関数) に置き換えられます。

返値

Promise が履行されるか拒否されると、それぞれのハンドラー関数 (onFulfilled または onRejected) が非同期に呼び出されます (現在のスレッドループにスケジュールされます)。ハンドラー関数のこの動作は特定の一連の規則に従います。もしハンドラー関数が・・・

  • 値を返した場合、 then によって返されるプロミスは返値をその値として解決します。
  • 何も返さなかった場合、 then によって返されるプロミスは undefined の値で解決します。
  • エラーを投げた場合、 then によって返されるプロミスは、その値としてエラーを投げて拒否されます。
  • すでに履行されたプロミスを返した場合、 then によって返されるプロミスは、そのプロミスの値をその値として返します。
  • すでに拒否されたプロミスを返した場合、 then によって返されるプロミスは、そのプロミスの値をその値として拒否されます。
  • 他の待機状態のプロミスオブジェクトを返した場合、 then によって返されたプロミスの解決/拒否は、ハンドラーによって返されたプロミスの解決/拒否結果に依存します。また、 then によって返されたプロミスの解決値は、ハンドラーによって返されたプロミスの解決値と同じになります。

以下は、 then メソッドの非同期性を示す例です。

// using a resolved promise, the 'then' block will be triggered instantly,
// but its handlers will be triggered asynchronously as demonstrated by the console.logs
const resolvedProm = Promise.resolve(33);

let thenProm = resolvedProm.then(value => {
    console.log("this gets called after the end of the main stack. the value received and returned is: " + value);
    return value;
});
// instantly logging the value of thenProm
console.log(thenProm);

// using setTimeout we can postpone the execution of a function to the moment the stack is empty
setTimeout(() => {
    console.log(thenProm);
});

// ログ(この順で)
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// "this gets called after the end of the main stack. the value received and returned is: 33"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}

解説

then メソッドや Promise.prototype.catch() メソッドはプロミスを返すので、連鎖可能です。 — これは合成と呼ばれる操作です。

then メソッドの使用

var p1 = new Promise((resolve, reject) => {
  resolve('Success!');
  // or
  // reject(new Error("Error!"));
});

p1.then(value => {
  console.log(value); // Success!
}, reason => {
  console.error(reason); // Error!
});

連鎖

then メソッドは Promise を返すので、メソッド連鎖ができます。

関数が then にハンドラーとして渡されると Promise を返します。同じ Promise がメソッド連鎖の次の then に現れます。次のスニペットは、非同期実行をシミュレートする、 setTimeout() 関数付きのコードです。

Promise.resolve('foo')
  // 1. Receive "foo", concatenate "bar" to it, and resolve that to the next then
  .then(function(string) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        string += 'bar';
        resolve(string);
      }, 1);
    });
  })
  // 2. receive "foobar", register a callback function to work on that string
  // and print it to the console, but not before returning the unworked on
  // string to the next then
  .then(function(string) {
    setTimeout(function() {
      string += 'baz';
      console.log(string); // foobarbaz
    }, 1)
    return string;
  })
  // 3. print helpful messages about how the code in this section will be run
  // before the string is actually processed by the mocked asynchronous code in the
  // previous then block.
  .then(function(string) {
    console.log("Last Then:  oops... didn't bother to instantiate and return " +
                "a promise in the prior then so the sequence may be a bit " +
                "surprising");

    // Note that `string` will not have the 'baz' bit of it at this point. This
    // is because we mocked that to happen asynchronously with a setTimeout function
    console.log(string); // foobar
  });

// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz

then ハンドラー内から値が返された場合は、 Promise.resolve (<ハンドラーが呼ばれて返された値>) が返されます。

var p2 = new Promise(function(resolve, reject) {
  resolve(1);
});

p2.then(function(value) {
  console.log(value); // 1
  return value + 1;
}).then(function(value) {
  console.log(value + ' - A synchronous value works'); // 2 - A synchronous value works
});

p2.then(function(value) {
  console.log(value); // 1
});

then の引数として渡した関数が拒否されたプロミスを返した場合や、例外 (エラー) が発生した場合は、拒否されたプロミスを返します。

Promise.resolve()
  .then(() => {
    // Makes .then() return a rejected promise
    throw new Error('Oh no!');
  })
  .then(() => {
    console.log('Not called.');
  }, error => {
    console.error('onRejected function called: ' + error.message);
  });

その他の場合はすべて、解決中 (resolving) のプロミスが返されます。次の例では、連鎖上の以前のプロミスが拒否されていても、最初の then() は解決中のプロミスに含まれた 42 を返します。

Promise.reject()
  .then(() => 99, () => 42) // onRejected returns 42 which is wrapped in a resolving Promise
  .then(solution => console.log('Resolved with ' + solution)); // Resolved with 42

多くの場合、 catch を使って失敗状態のプロミスを補足する方が、 then の 2 つのハンドラーを使って処理するよりも現実的です。下記の例を見てください。

Promise.resolve()
  .then(() => {
    // Makes .then() return a rejected promise
    throw new Error('Oh no!');
  })
  .catch(error => {
    console.error('onRejected function called: ' + error.message);
  })
  .then(() => {
    console.log("I am always called even if the prior then's promise rejects");
  });

Promise ベースの API を持った関数同士であれば、別の関数上に他の関数を実装することで連鎖を使うこともできます。

function fetch_current_data() {
  // The fetch() API returns a Promise.  This function
  // exposes a similar API, except the fulfillment
  // value of this function's Promise has had more
  // work done on it.
  return fetch('current-data.json').then(response => {
    if (response.headers.get('content-type') != 'application/json') {
      throw new TypeError();
    }
    var j = response.json();
    // maybe do something with j
    return j; // fulfillment value given to user of
              // fetch_current_data().then()
  });
}

onFulfilled がプロミスを返した場合、 then の返値はプロミスによって解決/拒否されます。

function resolveLater(resolve, reject) {
  setTimeout(function() {
    resolve(10);
  }, 1000);
}
function rejectLater(resolve, reject) {
  setTimeout(function() {
    reject(new Error('Error'));
  }, 1000);
}

var p1 = Promise.resolve('foo');
var p2 = p1.then(function() {
  // Return promise here, that will be resolved to 10 after 1 second
  return new Promise(resolveLater);
});
p2.then(function(v) {
  console.log('resolved', v);  // "resolved", 10
}, function(e) {
  // not called
  console.error('rejected', e);
});

var p3 = p1.then(function() {
  // Return promise here, that will be rejected with 'Error' after 1 second
  return new Promise(rejectLater);
});
p3.then(function(v) {
  // not called
  console.log('resolved', v);
}, function(e) {
  console.error('rejected', e); // "rejected", 'Error'
});

window.setImmediate 形式のプロミスベースの代替処理

Function.prototype.bind() を使用して、 Reflect.apply (Reflect.apply()) メソッドは (キャンセルできない) window.setImmediate (en-US) 形式の関数を作成することができます。

const nextTick = (() => {
  const noop = () => {}; // literally
  const nextTickPromise = () => Promise.resolve().then(noop);

  const rfab = Reflect.apply.bind; // (thisArg, fn, thisArg, [...args])
  const nextTick = (fn, ...args) => (
    fn !== undefined
    ? Promise.resolve(args).then(rfab(null, fn, null))
    : nextTickPromise(),
    undefined
  );
  nextTick.ntp = nextTickPromise;

  return nextTick;
})();

仕様書

Specification
ECMAScript Language Specification
# sec-promise.prototype.then

ブラウザーの互換性

BCD tables only load in the browser

関連情報