どうも JSネタです。
何も考えずに aync
await
してたら 参考にしたいソースコードが
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 成功
resolve('Async Hello world');
}, 16);
});
}
って書いてあって何も分からなくなってしまった。
理解ついでにコードを書きつつブログを書いていきます。
環境はnode.js+TypeScript(3.7.2) です。
TypeScriptで書いた方がわかりやすいので
もくじ
Promiseを返す関数の話
Promiseとは
- 非同期処理を「操作」するもの
- 成功時の処理、失敗時の処理を明示的に書ける。
簡単に書いてみる
function asyncFunction(): Promise<string> {
return new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('Async Hello World');
})
})
}
asyncFunction().then(function (val) {
console.log(val); // => 'Async Hello world'
});
まず、asyncFunctionはPromise
Promise
後はここ、ちゃんとPromise返ってくるのは伝わるね。
return new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('Async Hello World');
})
})
型が謎なんですけど関数の返り値にPromise<string>
を書いたタイミングでresolve
関数の
引数が決定される・・・?
基本的に実際の非同期処理はここに書いていて、setTimeoutの第一引数がhandlerになっている感じです。
setTimeout
の第二引数に入れた時間が経過したら第一引数の関数を呼び出す感じかな。
setTimeOutのhandlerでresolve
が呼び出せるの、正直スコープどうなってんねんって思います。
ちなみに直でこうも書けた。
new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('Async Hello World2');
})}).then(function (val) {
console.log(val); // => 'Async Hello World2'
});
ちゃんと理解するためにasyncFunction
を使って新しい関数を作ってみる。
function asyncFunction1(): Promise<string> {
return new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('Async Hello World');
})
})
}
function asyncFunction2(): Promise<string> {
return new Promise(function (resolve) {
asyncFunction1().then(function (val) {
resolve(val+'nyaan');
})
})
}
asyncFunction2().then(
function (val) {
console.log(val); // => Async Hello Worldnyaan
}
);
rejectしてみる
function rejectAsync() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error('Error'))
}, 10);
});
}
rejectAsync().then(function (val) {
console.log('絶対に呼ばれない')
}).catch(function (error) {
// 呼ばれる
console.log(error)
}
);
ちなみに出力は以下。
Error: Error
at Timeout._onTimeout (/Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:4:14)
at ontimeout (timers.js:436:11)
at tryOnTimeout (timers.js:300:5)
at listOnTimeout (timers.js:263:5)
at Timer.processTimers (timers.js:223:10)
Process finished with exit code 0
閑話休題
このa,b,c,fの4つは同じ動作をします。
function f() {
console.log('hello world');
}
const a=f;
a();
const b = ()=>{console.log('hello world')};
b();
const c = ()=>console.log('hello world');
c();
だけどこれはダメ
function x() console.log('hello world');
typescriptの場合は関数の後はコードブロック({}
のこと)しか受け付けてないんですかね・・・?
アロー関数ならコードブロックじゃなくても良くなるけど・・・
それはそうと、こっからはアロー関数使えるところは使っていきます。
実はrejectはなくても良い
無名関数書くのめんどくさいのでアロー関数で
new Promise(function(resolve) {
resolve(42);
}).then((val)=> console.log(val)); // => 42
逆にrejectだけでも良い
new Promise(function(reject) {
reject(new Error('42'));
}).then((val)=> console.log(val)); // => 42
> ts-node ./src/index.ts
Error: 42
at /Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:2:10
at new Promise (<anonymous>)
at Object.<anonymous> (/Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:1:1)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Module.m._compile (/Users/reud/WebstormProjects/typescript-promise-practice/node_modules/ts-node/src/index.ts:493:23)
at Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Object.require.extensions.(anonymous function) [as .ts] (/Users/reud/WebstormProjects/typescript-promise-practice/node_modules/ts-node/src/index.ts:496:12)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
Process finished with exit code 0
Promise.resolveの話
Promiseで非同期処理を書く時、ここまでなんとなくnew Promiseしていたけど、
Promiseオブジェクトを生成する際、Promise.resolveっていうのがある。
UTにめっちゃ使えそう
例えば、
Promise.resolve(42);
と
new Promise(resolve => resolve(42));
は同じ動作を行う。
けど、こうやると動作が変わる。
let fn = ()=>{throw new Error('error')};
let ret1 = new Promise((resolve => resolve(fn())));
let ret2 = Promise.resolve(fn()); // => 例外が発生
Promise.resolveって書いている以上失敗する可能性は書かない方が良さそうですね・・・
メソッドチェーン
Promiseオブジェクトに.then
でメソッドのチェーンができます。
let fn = ()=>console.log('hello world');
let fn2 = ()=>console.log('hello world2');
let fn3 = ()=>console.log('hello world3');
const promise:Promise<void> = Promise.resolve(fn());
promise.then(fn2).then(fn3);
/*
hello world
hello world2
hello world3
*/
ちなみに.catchはPromiseオブジェクトがしくった場合だけでなく、.thenでしくじっても
キャッチしてくれます。
(throw new Error()が一文じゃないのか、{}がfn2のアロー関数で必要だった
。)
そして、エラーの起きた.then以降は実行されなくなります。
let fn = ()=>console.log('hello world');
let fn2 = ()=> {throw new Error('error in fn2')};
let fn3 = ()=>console.log('hello world3');
const promise:Promise<void> = Promise.resolve(fn());
promise.then(fn2).then(fn3).catch((e)=> console.log(`error = ${e}`));
実行するとこう。
> ts-node ./src/index.ts
hello world
error = Error: error in fn2
逐次実行の理解はまた今度・・・
所感
JS書いてる人、高階関数強そう。