Promiseとは
前提
非同期処理はブラウザやnode.jsが提供する処理だが、PromiseはECMAScriptの標準ビルトインオブジェクトである。ので、非同期処理と密接な関係にあるPromiseだが、独立した機能である
Promiseの挙動
第一引数にcallback関数を入れる
このcallback関数はnewされた瞬間に実行される
let promise = new Promise(()=>{
console.log('new promise!');
});
インスタンス化されたオブジェクトが持つプロパティ
- PromiseState: Pending
- PromiseResult: undefined
プロパティの挙動
- callback関数の第一引数の関数オブジェクト(resolve)を実行
⇒PromiseState が fulfilledに ⇒引数がPromiseResultに入る
let promise = new Promise((resolve, reject)=>{
resolve('hello');
});
console.log(promise); // [[PromiseState]]: "fulfilled",[[PromiseResult]]: "hello"
- callback関数の第二引数の関数オブジェクト(reject)を実行
⇒PromiseState が rejectedに ⇒引数がPromiseResultに入る
let promise = new Promise((resolve, reject)=>{
reject('error');
});
console.log(promise); // [[PromiseState]]: "rejected",[[PromiseResult]]: "error"
then,catch,finallyの挙動
PromiseのPromiseStateプロパティによって実行されるメソッドが変わる
PromiseState: Pending
- then,catch,finallyに渡した関数を内部的に保管しておく(実行しない)
let promise = new Promise((resolve, reject)=>{});
promise.then(()=>{console.log('then')}); // 実行されない
promise.catch(()=>{console.log('catch')}); // 実行されない
promise.finally(()=>{console.log('finally')}); // 実行されない
PromiseState: fulfilled
- thenとfinallyが実行される(内部的に保管されたものも実行)
- thenは第一引数にPromiseResultを受け取れる
let promise = new Promise((resolve, reject)=>{
resolve('hello');
});
promise.then((value)=>{
console.log(value); // hello
});
promise.catch(()=>{}); //実行されない
promise.finally(()=>{
console.log('finally'); // finally
});
PromiseState: rejected
- catchとfinallyが実行される(内部的に保管されたものも実行)
- catchは第一引数にPromiseResultを受け取れる
let promise = new Promise((resolve, reject)=>{
reject(new Error('error'));
});
promise.then(()=>{}); // 実行されない
promise.catch((error)=>{
console.log(error.message); // error
});
promise.finally(()=>{
console.log('finally'); // finally
});
Promiseチェーンとは
定義
then,catch,finallyはチェーン状に繋いで記述することができること
特徴8つ
1. thenの中でreturnされたらプロミスチェーン上の次のthenが実行され、次のthenの第一引数にreturnされた値が入る
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
})
2. thenの中でthrowされたらプロミスチェーン上の次のcatchが実行され、次のcatchの第一引数にthrowされた値が入る
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
throw new Error(3);
})
.catch((error)=>{
console.log(error.message); // 3
})
3. catchの中でもthrowと同様の動きをする
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
throw new Error(3);
})
.catch((error)=>{
console.log(error.message); // 3
throw new Error(4);
})
.catch((error)=>{
console.log(error.message); // 4
return 5;
})
.then((val)=>{
console.log(val); // 5
})
4. thenやcatchでreturnされた後は次のthenが実行され、catchはskipする
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
return 3;
})
.catch(()=>{console.log('skip')}) // 実行されない
.then((val)=>{
console.log(val); // 3
})
5. thenやcatchでthrowされた後は次のcatchが実行され、thenはskipする
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
throw new Error(3);
})
.then(()=>{console.log('skip')}) // 実行されない
.catch((error)=>{
console.log(error.message); // 3
})
6. throwされた後にcatchがいないとエラーが出る
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
throw new Error(3);
})
.then(()=>{console.log('skip')});
// catchがいないのでエラーになる
7. finallyは順番が来たら実行されるが、値は受け取れない、その後のチェーンは続く
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
console.log(val); // 2
throw new Error(3);
})
.finally((val)=>{
console.log(val); // undefined
})
.catch((error)=>{
console.log(error.message); // 3
})
8. thenやcatchの中でPromiseがreturnされたらそのPromiseのresolve,rejectが実行されるのを待ち、resolveなら次はthenをrejectなら次はcatchを実行する
new Promise((resolve)=>resolve(1))
.then((val)=>{
console.log(val); // 1
return 2;
})
.then((val)=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(3);
}, 1000);
})
})
.then((val)=>{
console.log(val); // 1秒後に3が出力
return new Promise((resolve, reject)=>{
setTimeout(()=>{
reject(new Error(4));
}, 1000);
})
})
.catch((error)={
console.log(error.message); // 3が出力された1秒後に4が出力
})
Promiseの問題
ES2015で登場したのでそれ以前のwebAPIには対応していない
Promiseに対応した最新のwebAPIは内部的にPromiseを返してくれる
<例>
↓OSに繋がっているビデオやカメラを非同期で取得する
// このgetUserMediaメソッドはPromiseを返しているのでPromiseチェーンが使える
navigator.mediaDevices.getUserMedia({video:true})
.then((value)=>{
console.log(value); // videoの情報が取れればその情報が入る
})
.catch((error)=>{
console.log(error.message); // 情報が取れなければエラーオブジェクトが返される
})
↓上記の処理に加えてクリップ情報を取得したければ
navigator.mediaDevices.getUserMedia({video:true})
.then((value)=>{
console.log(value);
})
.catch((error)=>{
console.log(error.message);
})
.then(()=>{
return navigator.clipboard.readText(); // これもPromiseを返すのでresolveかrejectされるのを待つ
})
.then((text)=>{
console.log(text); // 上記が内部的にresolveされたらクリップボードの値が取れる
})
.catch((error)=>{
console.log(error.message); // rejectされたらこっち
})
古いAPIをPromise化する(Promisified)
setTimeoutをPromise化
let promisifiedSetTimeout = (time) => {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve();
}, time);
});
};
Promise化されたsetTimeoutを使う
promisifiedSetTimeout(1000)
.then(()=>{
console.log(1);
return promisifiedSetTimeout(1000);
})
.then(()=>{
console.log(2);
return promisifiedSetTimeout(1000);
})
.then(()=>{
console.log(3);
return promisifiedSetTimeout(1000);
})