サーチ…
構文
- 新しいプロミス(/ *実行者関数:* /関数(解決、拒否){})
- promise.then(onFulfilled [、onRejected])
- promise.catch(onRejected)
- Promise.resolve(解決策)
- Promise.reject(理由)
- Promise.all(iterable)
- Promise.race(iterable)
備考
約束はECMAScript 2015仕様の一部であり、 ブラウザのサポートは限られており、2017年7月現在世界中のブラウザの88%がサポートしています。次の表は、約束をサポートする最も初期のブラウザのバージョンの概要を示しています。
クロム | エッジ | Firefox | インターネットエクスプローラ | オペラ | Opera Miniは | サファリ | iOS Safari |
---|---|---|---|---|---|---|---|
32 | 12 | 27 | バツ | 19 | バツ | 7.1 | 8 |
それらをサポートしていない環境では、 Promise
はポリフェイルすることができます。サードパーティのライブラリは、コールバック関数の自動化された「約束」や、 notify
などのprogress
などの追加メソッドなどの拡張機能も提供します。
Promises / A +の標準Webサイトには、 1.0および1.1準拠の実装のリストがあります 。 A +標準に基づくプロミスコールバックは、イベントループで常にマイクロタスクとして非同期に実行されます 。
プロミスチェーン
then
、約束の方法は新しい約束を返す。
const promise = new Promise(resolve => setTimeout(resolve, 5000));
promise
// 5 seconds later
.then(() => 2)
// returning a value from a then callback will cause
// the new promise to resolve with this value
.then(value => { /* value === 2 */ });
返すPromise
からthen
、コールバックすることは約束チェーンに追加します。
function wait(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
}
const p = wait(5000).then(() => wait(4000)).then(() => wait(1000));
p.then(() => { /* 10 seconds have passed */ });
catch
どのように似て、拒否された約束が回復することができますcatch
でtry
/ catch
ステートメントが動作します。いずれかが連鎖しthen
の後にcatch
から解決された値使用して、その解決のハンドラを実行しますcatch
。
const p = new Promise(resolve => {throw 'oh no'});
p.catch(() => 'oh yes').then(console.log.bind(console)); // outputs "oh yes"
チェーンの途中にcatch
またはreject
ハンドラがない場合、最後のcatch
はチェーン内の拒否を捕捉します。
p.catch(() => Promise.reject('oh yes'))
.then(console.log.bind(console)) // won't be called
.catch(console.error.bind(console)); // outputs "oh yes"
場合によっては、関数の実行を「分岐」したい場合があります。あなたは、条件に応じて関数から異なる約束を返すことによってそれを行うことができます。コードの後半では、これらのブランチのすべてを1つに統合して、それらのブランチ上の他の関数を呼び出したり、すべてのエラーを1か所で処理したりすることができます。
promise
.then(result => {
if (result.condition) {
return handlerFn1()
.then(handlerFn2);
} else if (result.condition2) {
return handlerFn3()
.then(handlerFn4);
} else {
throw new Error("Invalid result");
}
})
.then(handlerFn5)
.catch(err => {
console.error(err);
});
したがって、関数の実行順序は次のようになります。
promise --> handlerFn1 -> handlerFn2 --> handlerFn5 ~~> .catch()
| ^
V |
-> handlerFn3 -> handlerFn4 -^
単一のcatch
は、発生する可能性がある分岐のいずれかでエラーを取得します。
前書き
Promise
オブジェクトは、値を生成したか、最終的に値を生成する操作を表します。プロミスは、非同期作業の結果(おそらく保留状態)を包み込み、深くネストされたコールバック(「 コールバック・ヘル 」と呼ばれる)の問題を緩和する強力な方法を提供します 。
状態と制御フロー
約束は、次の3つの状態のいずれかになります。
- 保留中 - 基本操作はまだ完了しておらず、約束は保留中です。
- fulfilled - オペレーションは終了し、約束は値で 満たされます 。これは、同期関数から値を返すことに似ています。
- rejected - 操作中にエラーが発生し、約束が理由で 拒否されました。これは、同期関数にエラーを投げることに似ています。
約束が成立したとき、または拒絶されたときに約束された(または解決された )という。約束がいったん解決すれば、それは不変になり、その状態は変わることができません。その約束のthen
メソッドとcatch
メソッドを使用して、決済時に実行されるコールバックをアタッチすることができます。これらのコールバックは、それぞれフルフィルメント値と拒否理由で呼び出されます。
例
const promise = new Promise((resolve, reject) => {
// Perform some work (possibly asynchronous)
// ...
if (/* Work has successfully finished and produced "value" */) {
resolve(value);
} else {
// Something went wrong because of "reason"
// The reason is traditionally an Error object, although
// this is not required or enforced.
let reason = new Error(message);
reject(reason);
// Throwing an error also rejects the promise.
throw reason;
}
});
then
およびcatch
メソッドは、フルフィルメントおよび拒否コールバックを添付するために使用できます。
promise.then(value => {
// Work has completed successfully,
// promise has been fulfilled with "value"
}).catch(reason => {
// Something went wrong,
// promise has been rejected with "reason"
});
注:同じ約束でpromise.then(...)
とpromise.catch(...)
を呼び出すと、約束を実行している間、またはコールバックの中でエラーが発生した場合、 Uncaught exception in Promise
が発生する可能性があります。好ましい方法は、以前から返された約束を次のリスナーを添付するだろうthen
/ catch
。
あるいは、両方のコールバックを1回の呼び出しでアタッチすると、次のようにthen
ます。
promise.then(onFulfilled, onRejected);
すでに確定されている約束にコールバックを添付すると、即座にそれらがマイクロタスクキューに置かれ、「できるだけ早く」(つまり、現在実行中のスクリプトの直後に)呼び出されます。他の多くのイベントを実行する実装とは異なり、コールバックをアタッチする前に約束の状態を確認する必要はありません。
遅延関数呼び出し
setTimeout()
メソッドは、指定されたミリ秒後に関数を呼び出したり、式を評価したりします。これは、非同期操作を実現するための些細な方法でもあります。
この例では、 wait
関数を呼び出すと、最初の引数として指定された時間後に約束が解決されます。
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
wait(5000).then(() => {
console.log('5 seconds have passed...');
});
同時に複数の約束を待っている
Promise.all()
静的メソッドは、約束のPromise.all()
例えばArray
)を受け取り、 Promise.all()
内のすべての約束が解決されたときに解決する新しい約束を返すか、またはイテラブル内の約束事の少なくとも1つが却下された場合に拒絶する。
// wait "millis" ms, then resolve with "value"
function resolve(value, milliseconds) {
return new Promise(resolve => setTimeout(() => resolve(value), milliseconds));
}
// wait "millis" ms, then reject with "reason"
function reject(reason, milliseconds) {
return new Promise((_, reject) => setTimeout(() => reject(reason), milliseconds));
}
Promise.all([
resolve(1, 5000),
resolve(2, 6000),
resolve(3, 7000)
]).then(values => console.log(values)); // outputs "[1, 2, 3]" after 7 seconds.
Promise.all([
resolve(1, 5000),
reject('Error!', 6000),
resolve(2, 7000)
]).then(values => console.log(values)) // does not output anything
.catch(reason => console.log(reason)); // outputs "Error!" after 6 seconds.
Promise.all([
resolve(1, 5000),
resolve(2, 6000),
{ hello: 3 }
])
.then(values => console.log(values)); // outputs "[1, 2, { hello: 3 }]" after 6 seconds
破壊の割り当ては、複数の約束から結果を取り出すのに役立ちます。
Promise.all([
resolve(1, 5000),
resolve(2, 6000),
resolve(3, 7000)
])
.then(([result1, result2, result3]) => {
console.log(result1);
console.log(result2);
console.log(result3);
});
最初の複数の約束を待っている
Promise.race()
静的メソッドは、Promiseの反復可能性を受け取り、反復可能性の中の最初の約束が解決または拒否されるとすぐに解決または拒否する新しいPromiseを返します。
// wait "milliseconds" milliseconds, then resolve with "value"
function resolve(value, milliseconds) {
return new Promise(resolve => setTimeout(() => resolve(value), milliseconds));
}
// wait "milliseconds" milliseconds, then reject with "reason"
function reject(reason, milliseconds) {
return new Promise((_, reject) => setTimeout(() => reject(reason), milliseconds));
}
Promise.race([
resolve(1, 5000),
resolve(2, 3000),
resolve(3, 1000)
])
.then(value => console.log(value)); // outputs "3" after 1 second.
Promise.race([
reject(new Error('bad things!'), 1000),
resolve(2, 2000)
])
.then(value => console.log(value)) // does not output anything
.catch(error => console.log(error.message)); // outputs "bad things!" after 1 second
「約束する」値
Promise.resolve
静的メソッドを使用して、値を約束にラップすることができます。
let resolved = Promise.resolve(2);
resolved.then(value => {
// immediately invoked
// value === 2
});
value
がすでに約束されている場合、 Promise.resolve
単にそれを再作成します。
let one = new Promise(resolve => setTimeout(() => resolve(2), 1000));
let two = Promise.resolve(one);
two.then(value => {
// 1 second has passed
// value === 2
});
実際には、 value
は、 "thenable"(spec準拠の約束と同様に十分機能するthen
メソッドを定義するオブジェクト)になります。これにより、 Promise.resolve
は、信頼できない第三者のオブジェクトを信頼できる第三者の約束に変換することができます。
let resolved = Promise.resolve({
then(onResolved) {
onResolved(2);
}
});
resolved.then(value => {
// immediately invoked
// value === 2
});
Promise.reject
静的メソッドは、特定のreason
ですぐに拒否する約束を返します。
let rejected = Promise.reject("Oops!");
rejected.catch(reason => {
// immediately invoked
// reason === "Oops!"
});
コールバックによる「特化」機能
ノードスタイルのコールバックを受け入れる関数が与えられた場合、
fooFn(options, function callback(err, result) { ... });
これを約束することができます(約束事に基づく関数に変換する) 。
function promiseFooFn(options) {
return new Promise((resolve, reject) =>
fooFn(options, (err, result) =>
// If there's an error, reject; otherwise resolve
err ? reject(err) : resolve(result)
)
);
}
この関数は、次のように使用できます。
promiseFooFn(options).then(result => {
// success!
}).catch(err => {
// error!
});
もっと一般的な方法として、与えられたコールバックスタイルの関数を約束する方法は次のとおりです:
function promisify(func) {
return function(...args) {
return new Promise((resolve, reject) => {
func(...args, (err, result) => err ? reject(err) : resolve(result));
});
}
}
これは次のように使用できます:
const fs = require('fs');
const promisedStat = promisify(fs.stat.bind(fs));
promisedStat('/foo/bar')
.then(stat => console.log('STATE', stat))
.catch(err => console.log('ERROR', err));
エラー処理
約束からスローされたエラーは、(第2パラメータによって処理されるreject
)に渡されたthen
またはに渡されたハンドラによってcatch
:
throwErrorAsync()
.then(null, error => { /* handle error here */ });
// or
throwErrorAsync()
.catch(error => { /* handle error here */ });
チェイニング
プロミスチェーンを使用している場合、エラーが発生resolve
とresolve
ハンドラはスキップされます。
throwErrorAsync()
.then(() => { /* never called */ })
.catch(error => { /* handle error here */ });
同じことがthen
機能にも当てはまります。 resolve
ハンドラが例外をスローすると、次のreject
ハンドラが呼び出されます。
doSomethingAsync()
.then(result => { throwErrorSync(); })
.then(() => { /* never called */ })
.catch(error => { /* handle error from throwErrorSync() */ });
エラーハンドラは新しい約束を返し、約束を続けることができます。エラーハンドラによって返された約束は、ハンドラによって返された値で解決されます。
throwErrorAsync()
.catch(error => { /* handle error here */; return result; })
.then(result => { /* handle result here */ });
エラーを再スローすることで、エラーを約束することができます:
throwErrorAsync()
.catch(error => {
/* handle error from throwErrorAsync() */
throw error;
})
.then(() => { /* will not be called if there's an error */ })
.catch(error => { /* will get called with the same error */ });
setTimeout
コールバック内にthrow
文をラップすることで、promiseによって処理されない例外をスローすることができます。
new Promise((resolve, reject) => {
setTimeout(() => { throw new Error(); });
});
これは、プロビジョニングが非同期にスローされた例外を処理できないために機能します。
未処理の拒否
promiseにcatch
ブロックまたはreject
ハンドラがない場合、エラーは暗黙のうちに無視されます。
throwErrorAsync()
.then(() => { /* will not be called */ });
// error silently ignored
これを防ぐには、常にcatch
ブロックを使用します。
throwErrorAsync()
.then(() => { /* will not be called */ })
.catch(error => { /* handle error*/ });
// or
throwErrorAsync()
.then(() => { /* will not be called */ }, error => { /* handle error*/ });
また、に加入unhandledrejection
未処理の拒否約束をキャッチするイベント:
window.addEventListener('unhandledrejection', event => {});
いくつかの約束は、彼らの創造時より後に拒絶を処理することができます。そのような約束事が処理されるときはいつでも、 rejectionhandled
イベントが発生します。
window.addEventListener('unhandledrejection', event => console.log('unhandled'));
window.addEventListener('rejectionhandled', event => console.log('handled'));
var p = Promise.reject('test');
setTimeout(() => p.catch(console.log), 1000);
// Will print 'unhandled', and after one second 'test' and 'handled'
event
引数には拒否に関する情報が含まれます。 event.reason
はエラーオブジェクトで、 event.promise
はイベントを引き起こした約束オブジェクトです。
Nodejsにrejectionhandled
とunhandledrejection
イベントが呼び出されるrejectionHandled
とunhandledRejection
上のprocess
、それぞれ異なる署名を有します。
process.on('rejectionHandled', (reason, promise) => {});
process.on('unhandledRejection', (reason, promise) => {});
reason
引数はエラーオブジェクトであり、 promise
引数はイベントを発生させたpromiseオブジェクトへの参照です。
これらのunhandledrejection
ていないrejectionhandled
イベントとrejectionhandled
unhandledrejection
イベントは、デバッグの目的でのみ使用する必要があります。通常、すべての約束は拒絶を処理するべきです。
注:現時点では、Chrome 49+とNode.jsのみがunhandledrejection
rejectionhandled
イベントとrejectionhandled
unhandledrejection
イベントをサポートしています。
警告
fulfill
てreject
連鎖reject
then(fulfill, reject)
関数(両方のパラメータがnull
ない)は、一意かつ複雑な振る舞いを持ち、正確な動作が分からない限りは使用しないでください。
入力の1つにnull
が指定されていると、関数は期待通りに機能しnull
。
// the following calls are equivalent
promise.then(fulfill, null)
promise.then(fulfill)
// the following calls are also equivalent
promise.then(null, reject)
promise.catch(reject)
しかし、両方の入力が与えられたとき、それは独特の挙動をとる:
// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.then(fulfill).catch(reject)
// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.catch(reject).then(fulfill)
then(fulfill, reject)
関数は、 then(fulfill).catch(reject)
ショートカットのように見えますが、互換性がないと問題を引き起こします。このような問題の一つは、ということですreject
ハンドラはからのエラー処理しないfulfill
ハンドラを。起こることは次のとおりです。
Promise.resolve() // previous promise is fulfilled
.then(() => { throw new Error(); }, // error in the fulfill handler
error => { /* this is not called! */ });
上記のコードは、エラーが伝搬されるため、約束が拒否されます。これを次のコードと比較すると、約束が成就します。
Promise.resolve() // previous promise is fulfilled
.then(() => { throw new Error(); }) // error in the fulfill handler
.catch(error => { /* handle error */ });
同様の問題はthen(fulfill, reject)
された約束の代わりに約束された約束を伝播することを除いて、 catch(reject).then(fulfill)
。
同期的に約束を返すべき関数から投げる
このような関数を想像してみてください。
function foo(arg) {
if (arg === 'unexepectedValue') {
throw new Error('UnexpectedValue')
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
}
もしそのような関数がpromiseチェーンの途中で使われるなら、明らかに問題はありません:
makeSomethingAsync().
.then(() => foo('unexpectedValue'))
.catch(err => console.log(err)) // <-- Error: UnexpectedValue will be caught here
しかし、同じ関数がpromise chainの外で呼び出された場合、エラーはそれによって処理されず、アプリケーションにスローされます。
foo('unexpectedValue') // <-- error will be thrown, so the application will crash
.then(makeSomethingAsync) // <-- will not run
.catch(err => console.log(err)) // <-- will not catch
回避策は2つあります。
エラーで拒否された約束を返す
スローするのではなく、次のようにします。
function foo(arg) {
if (arg === 'unexepectedValue') {
return Promise.reject(new Error('UnexpectedValue'))
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
}
あなたの機能を約束する
あなたのthrow
ステートメントは、すでにプロミスチェーン内にあるときに適切に捕捉されます:
function foo(arg) {
return Promise.resolve()
.then(() => {
if (arg === 'unexepectedValue') {
throw new Error('UnexpectedValue')
}
return new Promise(resolve =>
setTimeout(() => resolve(arg), 1000)
)
})
}
同期操作と非同期操作の調整
場合によっては、コード・ブランチでの反復を防ぐために、同期操作を約束の中にラップしたい場合があります。この例を考えてみましょう:
if (result) { // if we already have a result
processResult(result); // process it
} else {
fetchResult().then(processResult);
}
上記のコードの同期および非同期の分岐は、同期操作を約束の中に冗長にラップすることで調整できます。
var fetch = result
? Promise.resolve(result)
: fetchResult();
fetch.then(processResult);
非同期呼び出しの結果をキャッシュするときは、結果自体ではなく約束をキャッシュすることが望ましい。これにより、複数の並列要求を解決するために必要な非同期操作が1つだけになります。
エラー状態が発生したときにキャッシュされた値を無効にするように注意する必要があります。
// A resource that is not expected to change frequently
var planets = 'http://swapi.co/api/planets/';
// The cached promise, or null
var cachedPromise;
function fetchResult() {
if (!cachedPromise) {
cachedPromise = fetch(planets)
.catch(function (e) {
// Invalidate the current result to retry on the next fetch
cachedPromise = null;
// re-raise the error to propagate it to callers
throw e;
});
}
return cachedPromise;
}
連鎖を約束するように配列を減らす
このデザインパターンは、要素のリストから一連の非同期アクションを生成するのに便利です。
2つの変種があります:
- チェーンが成功を収めている限り継続するチェーンを構築する「引き下げ」の削減です。
- チェーンがエラーを経験している限り継続するチェーンを構築する「キャッチ」リダクション。
"then" reduction
パターンのこの変形は、 .then()
チェーンを構築し、アニメーションを連鎖させたり、一連の従属HTTPリクエストを作成するために使用されます。
[1, 3, 5, 7, 9].reduce((seq, n) => {
return seq.then(() => {
console.log(n);
return new Promise(res => setTimeout(res, 1000));
});
}, Promise.resolve()).then(
() => console.log('done'),
(e) => console.log(e)
);
// will log 1, 3, 5, 7, 9, 'done' in 1s intervals
説明:
- 我々は、呼び出し
.reduce()
ソースアレイ上、及び提供Promise.resolve()
初期値として。 - すべての要素を減らすと、
.then()
が初期値に追加されます。 -
reduce()
の商品はPromise.resolve()、then(...)、then(...)になります。 - 私たちは、手動で追加
.then(successHandler, errorHandler)
減らす後に実行するために、successHandler
以前のすべての手順が解決したら。いずれかのステップが失敗すると、errorHandler
が実行されます。
注: "then"リダクションは、 Promise.all()
シーケンシャルカウンターパートです。
「キャッチ」削減
パターンのこの変形は、 .catch()
チェーンを構築し、動作中のサーバが見つかるまで、一連のWebサーバを一連のミラー化されたリソースに対して順次探索するために使用されることがあります。
var working_resource = 5; // one of the values from the source array
[1, 3, 5, 7, 9].reduce((seq, n) => {
return seq.catch(() => {
console.log(n);
if(n === working_resource) { // 5 is working
return new Promise((resolve, reject) => setTimeout(() => resolve(n), 1000));
} else { // all other values are not working
return new Promise((resolve, reject) => setTimeout(reject, 1000));
}
});
}, Promise.reject()).then(
(n) => console.log('success at: ' + n),
() => console.log('total failure')
);
// will log 1, 3, 5, 'success at 5' at 1s intervals
説明:
- 我々は、呼び出し
.reduce()
ソースアレイ上、及び提供Promise.reject()
初期値として。 - すべての要素を減らすと、
.catch()
が初期値に追加されます。 -
reduce()
の製品はPromise.reject().catch(...).catch(...)
ます。 - 我々は、手動で追加する
.then(successHandler, errorHandler)
実行するために、削減後successHandler
、前のステップのいずれかが解決した後。すべてのステップが失敗すると、errorHandler
が実行されます。
注意:「キャッチ」削減は、 Promise.any()
逐次的な対応ですPromise.any()
bluebird.js
で実装されていますが、現在ネイティブのECMAScriptでは実装されていません)。
forEachと約束
配列の各要素に約束を返す関数( cb
)を効果的に適用することができます。各要素は、前の要素が処理されるまで処理されるのを待っています。
function promiseForEach(arr, cb) {
var i = 0;
var nextPromise = function () {
if (i >= arr.length) {
// Processing finished.
return;
}
// Process next function. Wrap in `Promise.resolve` in case
// the function does not return a promise
var newPromise = Promise.resolve(cb(arr[i], i));
i++;
// Chain to finish processing.
return newPromise.then(nextPromise);
};
// Kick off the chain.
return Promise.resolve().then(nextPromise);
};
これは、一度に1つずつ、何千ものアイテムを効率的に処理する必要がある場合に役立ちます。定期的なfor
ループを使用して約束を作成すると、それらをまとめて作成し、かなりの量のRAMを占有します。
finally()でクリーンアップを実行する
現在、約束が満たされたか拒否されたかにかかわらず実行される約束にfinally
コールバックを追加する提案 (ECMAScript標準の一部ではない)があります。意味的には、これはtry
ブロックのfinally
節に似ています 。
通常、この機能をクリーンアップに使用します。
var loadingData = true;
fetch('/data')
.then(result => processData(result.data))
.catch(error => console.error(error))
.finally(() => {
loadingData = false;
});
finally
コールバックは約束の状態に影響しないことに注意することが重要です。それがどんな価値を返すかは関係ありません。約束は以前にあった満たされた/拒絶された状態のままです。したがって、上記の例では、 finally
コールバックがundefined
返しても、 processData(result.data)
はprocessData(result.data)
戻り値で解決されます。
標準化プロセスがまだ進行中であるため、あなたの約束の実装はfinally
コールバックをそのまま受け入れないでしょう。同期コールバックの場合は、ポリフィルを使用してこの機能を追加できます。
if (!Promise.prototype.finally) {
Promise.prototype.finally = function(callback) {
return this.then(result => {
callback();
return result;
}, error => {
callback();
throw error;
});
};
}
非同期APIリクエスト
これは、非同期機能を利用することを約束した簡単なGET
API呼び出しの例です。
var get = function(path) {
return new Promise(function(resolve, reject) {
let request = new XMLHttpRequest();
request.open('GET', path);
request.onload = resolve;
request.onerror = reject;
request.send();
});
};
より堅牢なエラー処理は、次のonload
関数とonerror
関数を使用して実行できます。
request.onload = function() {
if (this.status >= 200 && this.status < 300) {
if(request.response) {
// Assuming a successful call returns JSON
resolve(JSON.parse(request.response));
} else {
resolve();
} else {
reject({
'status': this.status,
'message': request.statusText
});
}
};
request.onerror = function() {
reject({
'status': this.status,
'message': request.statusText
});
};
ES2017 async / awaitを使用する
上の同じ例のImage loadingは 、 async関数を使って記述することができます。これにより、例外処理のために共通のtry/catch
メソッドを使用することもできます。
注: 2017年4月現在、Internet Explorer以外のすべてのブラウザの現在のリリースでは、非同期機能がサポートされています 。
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', () => {
reject(new Error(`Failed to load ${url}`));
});
img.src = url;
});
}
(async () => {
// load /image.png and append to #image-holder, otherwise throw error
try {
let img = await loadImage('http://example.com/image.png');
document.getElementById('image-holder').appendChild(img);
}
catch (error) {
console.error(error);
}
})();