Promise, async/awaitを使ったJavaScriptの非同期処理
【所要時間】
およそ3時間(2019年1月5, 7日)
【概要】
- Node.jsを学ぶ-The definitive Node.js handbook-4 , Node.jsを学ぶ-The definitive Node.js handbook-5 , Node.jsを学ぶ-The definitive Node.js handbook-6で学んだPromiseを理解する。
【要約・学んだこと】
色々非同期処理について書かれたものを読んで少し理解できた。
JSでは普通に記述すると同期処理( synchronous)を行うので、上から順番に処理されるイメージ。
console.log(1);
console.log(2);//result
1
2
- setTimeoutを使って実行までに時間を作る。setTimeoutの時間を0mm秒にしても、イベントループの次の反復で実行されるため、この場合4よりも後に実行される。
setTimeout(() => {console.log(1)}, 1000)console.log(2)setTimeout(() => {console.log(3)}, 0)console.log(4)//result
2
4
3
1
promiseについて
promise、then , catchを使うことで非同期に処理をすることが可能。
- new Promiseを宣言することで、処理が成功したときは(解決)、または失敗したときはreject(拒否)が実行されるまで次の処理を待つことができる。
- 処理が成功した場合はthenを返し、エラーになったらcatchを返す。thenの場合はresolve, catchの場合はrejectの値を引数にすることができる。
thenについて
次のpromiseの内容はsetTimeoutを使って1000mm秒後console.logを実行する関数。
thenは通常の関数。
ここでは1000mm秒後にresolveが実行されるので、1000mm秒後に”promiseのresolved1”が表示され、その直後に”thenのresolved1”が表示される。
thenの引数resultがresolveの中身になっているのがわかる。
const test = new Promise(resolve => {
const resolveValue = "resolved1"
setTimeout(() => {
console.log(`promiseの${resolveValue}`);
resolve(resolveValue); //1000mm秒後に実行される。
},1000);
});test
.then(result => {
console.log(`thenの${result}`);
});//結果
Promise {<pending>}
promiseのresolved1
thenのresolved1
次の例ではresolveがsetTimeoutにないので即実行される。resolveが実行されるとthenに進むので、この場合thenの内容が先に表示される。
const test = new Promise(resolve => {
const resolveValue = "resolved1"
setTimeout(() => {
console.log(`promiseの${resolveValue}`);
},1000);
resolve(resolveValue); //即実行される。
});test
.then(result => {
console.log(`thenの${result}`);
});//結果
thenのresolved1
Promise {<resolved>: undefined}
VM1129:4 promiseのresolved1
thenはpromiseを返すので、連結させることで順番に処理させていくことが可能。メソッドチェーンと呼ぶ。ここでは上から順番に1000mm秒後に次の処理が実行される。
const test = new Promise(resolve => {
setTimeout(() => {
const resolveValue = "resolved1"
console.log(`promiseの${resolveValue}`);
resolve(resolveValue);
},1000);
});test
.then(result => {
return new Promise(resolve => { //new Promiseを改めて宣言
setTimeout(() => {
console.log(`then1の${result}`);
resolve(result)
}, 1000);
});
})
.then(result => {
setTimeout(() => {
console.log(`then2の${result}`);
}, 1000);
});//結果
Promise {<pending>}
promiseのresolved1
then1のresolved1
then2のresolved1
しかし、このようにpromiseを新たに宣言せずに下記のようにするとうまくいかない。先に2番目のthenの処理が実行される。
thenの引数をreturnで返すことで、つぎのthenの引数に渡すことが可能。
const test = new Promise(resolve => {
setTimeout(() => {
const resolveValue = "resolved1"
console.log(`promiseの${resolveValue}`);
resolve(resolveValue);
},1000);
});test
.then(result => {
setTimeout(() => { //new Promiseを宣言していない。
console.log(`then1の${result}`);
}, 1000);
return result
}).then(result => {
console.log(`then2の${result}`);
});//結果
Promise {<pending>}
promiseのresolved1
then2のresolved1
then1のresolved1
returnで返さないと、引数は次のthenに渡されない。
const test = new Promise(resolve => {
setTimeout(() => {
const resolveValue = "resolved1"
console.log(`promiseの${resolveValue}`);
resolve(resolveValue);
},1000);
});test
.then(result => {
setTimeout(() => {
console.log(`then1の${result}`);
}, 1000); //resultをreturnで返していない。
})
.then(result => {
console.log(`then2の${result}`);
});//結果
Promise {<pending>}
promiseのresolved1
then2のundefined
then1のresolved1
catchについて
処理が失敗した場合はthenではなくcatchを実行する。(いい例が思い浮かばなかったが、new Promiseでrejectが実行された場合に、catchが実行されていることはわかる。)
const bool = false;const testResove = new Promise((resolve, reject) => {
const res = "resolved";
const rej = "rejected";
console.log(bool);bool === true ?
resolve(res) :
reject(rej);
});testResove
.then(result => {
console.log(`then ${result}`)
})
.catch(result => {
console.log(`catch ${result}`)
});//結果
false
catch rejected
const boolがtrueなら下記のようにthenが実行される。
const bool = true;const testResove = new Promise((resolve, reject) => {
const res = "resolved";
const rej = "rejected";
console.log(bool);bool === true ?
resolve(res) :
reject(rej);
});testResove
.then(result => {
console.log(`then ${result}`)
})
.catch(result => {
console.log(`catch ${result}`)
});//結果
true
then resolved
async/awaitについて
基本的にはpromiseと同じだが、記述方法が異なるようなイメージ。より読みやすいコードになる。
- asyncと関数の前に記述すると、その関数はPromiseを返すようになる。
- awaitをPromise処理が記述された関数の前に記述すると、結果が返ってくるまで待機する。
- awaitはasyncで定義された関数内でのみ使うことができる。
const promiseTest = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log("promise");
resolve("promised");
}, 1000);
});
};const doSomething = async () => {
console.log(1);
console.log(await promiseTest());
console.log(2);
console.log(await promiseTest());
}
doSomething();//結果1
Promise {<pending>}
promise
promised
2
promise
promised
async内の関数は上から順番に処理されているのがわかる。
先ほどのpromiseと同じように書くとpromiseTestのresolveしか二度目の呼び出しで出力されない。(よくわかっていないので、returnを使って書くようにする。)
const promiseTest = new Promise(resolve => {
setTimeout(() => {
console.log("promise");
resolve("promised");
}, 1000);
});const doSomething = async () => {
console.log(1);
console.log(await promiseTest);
console.log(2);
console.log(await promiseTest);
}
doSomething();//結果
1
Promise {<pending>}
promise
promised
2
promised
例その2:
const asyncTest = async () => {
console.log("asyncTest");
const test1 = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log("test1");
resolve("test1");
}, 1000);
});
};const test2 = () => {
return new Promise(resolve => {
console.log("test2");
resolve("test2");
});
}; await test1();
console.log(1);
await test2();
console.log(2);
}asyncTest();//結果asyncTest
Promise {<pending>}
test1
1
test2
2
await1, await2の実行順を逆にすると結果も逆になるので、順番に処理されていることがわかる。
const asyncTest = async () => {
console.log("asyncTest");
const test1 = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log("test1");
resolve("test1");
}, 1000);
});
};const test2 = () => {
return new Promise(resolve => {
console.log("test2");
resolve("test2");
});
}; await test2();
await test1();
}asyncTest();//結果asyncTest
test2
1
Promise {<pending>}
test1
2
例その3:
const promiseTest = (res) => {
return new Promise(resolve => {
setTimeout(() => {
console.log("promiseTest");
resolve(`promise ${res}`);
}, 1000);
});
};
const asyncTest = async () => {
console.log("asyncTest")
console.log(await promiseTest(1));
console.log("asyncTest2");
console.log(await promiseTest(2));
};
asyncTest();//結果
asyncTest
Promise {<pending>}
promiseTest
promise 1
asyncTest2
promiseTest
promise 2
また、async/awaitでは処理の失敗でtry/catchが使える。
ここではtryのconst intentionalErrにErrという定義していない変数を入れてエラーを意図的に出した。するとcatchが返ってくる。(使い方はあってるのだろうか?)
const promiseTest = (res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("promiseTest");
resolve(`promise ${res}`);
}, 1000);
});
};
const asyncTest = async () => {
try {
const intentionalErr = Err + 1;
console.log("asyncTest")
console.log(await promiseTest(1));
console.log("asyncTest2");
console.log(await promiseTest(2));
} catch(err) {
console.log("err")
}
};
asyncTest();//結果
err
intentionalErrを修正するとtryの内容が返ってくる。
const promiseTest = (res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("promiseTest");
resolve(`promise ${res}`);
}, 1000);
});
};
const asyncTest = async () => {
try {
const intentionalErr = 1;
console.log("asyncTest")
console.log(await promiseTest(1));
console.log("asyncTest2");
console.log(await promiseTest(2));
} catch(err) {
console.log("err")
}
};
asyncTest();//結果
asyncTest
Promise {<pending>}
promiseTest
promise 1
asyncTest2
promiseTest
promise 2
【わからなかったこと】
- async/awaitを使うとなぜかしっくりこないが、記述方法が違う以外の注意点はなさそうなので、慣れていこうと思う。
【感想】
- async/awaitはpromiseの代わりというより、thenの代わりというイメージ。
- 実際に非同期処理が必要となるのは、データの取得が終わったら次の処理を行うといったような場面になると思われる。次はサーバー通信を行った実例を作ってみようかと思う。