Promise, async/await | JavaScript
Ref
- https://ja.javascript.info/promise-basics
- https://azu.github.io/promises-book/#introduction
let promise = new Promise(function(resolve, reject) {
// executor (生成コード, “シンガー”)
});
new Promise へ渡される関数は executor(執行者) と呼ばれます。new promise が作成されると、executor は自動的に実行されます。それは最終的に結果を生成コードを含んでいます。上記の例の言葉では、executor は “シンガー” です。
ref. https://ja.javascript.info/promise-basics
Promise
非同期処理の実行順序については以下を参照。
- https://ja.javascript.info/promise-basics
- https://azu.github.io/promises-book/
補足(修正点・秘匿情報): 元の記載では個人のChatGPT会話への非公開リンクが参照として貼られていましたが、他者からはアクセスできない私的なリンクのため、公開されている参考資料へのリンクに差し替えました。
基本
https://jsfiddle.net/ で確認。 DevToolsのConsoleではうまく確認できません。
// https://ja.javascript.info/promise-basics
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((Math.random()) > 0.5) {
resolve('RESOLVE!!')
} else {
reject('REJECT!!')
}
}, 1000);
});
}
const promise = createPromise();
promise
.then(
// 第一引数のコールバックはresolveによって呼び出される
(value) => {
console.log(value);
},
// 第2引数はのコールバックはrejectによって呼び出される
(value) => {
console.log(value)
});
ref. Window: setTimeout() メソッド
以下は上記の糖衣構文。
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((Math.random()) > 0.5) {
resolve('RESOLVE!!')
} else {
reject('REJECT!!')
}
}, 1000);
});
}
const promise = createPromise();
promise
.then(
// 第一引数のコールバックはresolve
(value) => {
console.log(value);
}
)
// thenの第2引数の代わりにrejectをcatchで受ける
// catchは必ずしもErrorオブジェクトではない(例ではString)
.catch((err) => { console.log(err) });
catchの例2。Errorオブジェクトをcatchで捕捉する。
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((Math.random()) > 0.5) {
resolve('RESOLVE!!')
} else {
reject(new Error('Error occurs'))
}
}, 1000);
});
}
const promise = createPromise();
promise
.then(
// 第一引数のコールバックはresolve
(value) => {
console.log(value);
}
)
// thenの第2引数の代わりにrejectをcatchで受ける
// catchは必ずしもErrorではない
.catch((err) => { console.log(err.message) });
補足(修正点): 元のサンプルは
ifの後にelseがなく、resolve(...)の直後で無条件にreject(...)も呼び出していました。Promise は一度 settle(resolve/reject)すると以降の resolve/reject 呼び出しは無視されるため、実行結果自体は偶然変わりませんが、読み手が誤解しやすいコードでした。elseを補って意図を明確にしています。
コールバックが実行されるタイミング
Promiseはインスタンスを生成した時点でコンストラクタへ渡したコールバックが実行される。
/*
* Promiseはインスタンスを生成した時点で
* コンストラクタへ渡したコールバックが実行される
*
* コメント数字は実行順序
* promise.thenが3, 4より遅く(非同期に)実行される理由は下記のリンクを参照
* https://azu.github.io/promises-book/#promise-is-always-async
*/
const promise = new Promise(resolve => {
console.log('first.'); // ----- (1)
resolve('done.');
});
console.log('something 1.'); // ----- (2)
/*
* thenは非同期に実行
*/
promise.then((data) => {
console.log(data) // ----- (5)
});
console.log('something 2.') // ----- (3)
console.log('something 3.') // ----- (4)
// first.
// something 1.
// something 2.
// something 3.
// done.
Promiseチェーン
/*
* Promise の例
* 直列処理で1秒間隔で2回日付を表示し最後に complete を出力
*/
function showFirstTime() {
return new Promise(resolve => {
setTimeout(() => {
resolve('First Time: ' + new Date().toString());
},
1000);
});
}
function showSecondTime() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Second Time: ' + new Date().toString());
}, 1000);
});
}
// ここから実行
showFirstTime()
.then((value) => {
console.log(value);
return showSecondTime();
})
.then((value) => {
console.log(value);
console.log('complete');
});
// First Time: Sat Dec 17 2022 15:55:32 GMT+0900 (日本標準時)
// Second Time: Sat Dec 17 2022 15:55:33 GMT+0900 (日本標準時)
// complete
Async/Await
非同期処理を分かりやすく記載できます。
基本
- await f() の戻り値は resolve された値
- await f() で f() がrejectされた場合は例外が発生するので try catch で処理
/*
* await は async の中で使用できる
* const result = await f(); の result は resolve された値を取得する
*
*/
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((Math.random()) > 0.5) {
resolve('RESOLVE!!')
} else {
reject('REJECT!!')
}
}, 1000);
});
}
async function sample() {
/*
* await promise は resolve の場合は値を返す
* reject の場合はエラーをスローするので catch で上取れる(例では err は文字列 REJECT!! になる
*/
try {
const result = await createPromise();
console.log(result)
} catch(err) {
console.log(err)
}
}
sample();
console.log('Something.');
// Something. <---- こちらが先に表示
// RESOLVE!! or REJECT!!
補足(修正点): 元のコードには
console.log('Something.')の呼び出しがなく、コメントの「Something. が先に表示される」という説明と実際のコードが対応していませんでした。呼び出しを追加しています。
サンプル
async 自体は非同期
function createPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((Math.random()) > 0.5) {
resolve('RESOLVE!!')
} else {
reject('REJECT!!')
}
}, 1000);
});
}
/*
* async 自体は同期的ではない
* 後述の console.log('Something.') が先に実行される
*/
(async () => {
/*
* await promise は resolve の場合は値を返す
* reject の場合はエラーをスローするので catch で受け取れる(例では err は文字列 REJECT!! になる
*/
try {
const result = await createPromise();
console.log(result)
} catch (err) {
console.log(err)
}
})();
console.log('Something.');
// Something. <---- こちらが先に表示
// RESOLVE!! or REJECT!!
await
/*
* async/await
* https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
* TypeScriptではエラー バージョンの問題か
*/
function resolveAfter1Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 1000);
});
}
async function add1(x) {
var a = resolveAfter1Seconds(20);
var b = resolveAfter1Seconds(30);
return x + await a + await b;
}
add1(10).then(v => {
console.log(v); // prints **60** after 1 seconds.
});
async function add2(x) {
var a = await resolveAfter1Seconds(20);
var b = await resolveAfter1Seconds(30);
return x + a + b;
}
add2(10).then(v => {
console.log(v); // prints **60** after 2 seconds.
});
補足(修正点): 元のコードでは
add2の結果が「70」と記載されていましたが、x + a + bは10 + 20 + 30 = 60でありadd1と同じ結果になります(異なるのは合計時間で、add1は並行実行のため約1秒、add2は逐次実行のため約2秒かかる点です)。「70」は誤りだったため「60」に修正しました。
日付表示
/*
* Async/Await
* 直列処理で1秒間隔で2回日付を表示
*
* 別サンプルで下記も参照
* https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
*/
function nowTime() {
return new Promise(resolve => {
window.setTimeout(() => {
console.log("Time: " + new Date().toString());
resolve();
},
1000);
});
}
async function showTime() {
await nowTime();
await nowTime();
}
// ここから実行
showTime();
サンプル
function createTimer() {
return new Promise( (resolve, reject) => {
setTimeout( () => resolve( 'aaa' ), 3000 )
} );
}
async function sync() {
const response = await createTimer();
console.log(response);
return 'bbb'
}
sync().then((value) => {
console.log(value);
})
console.log('zzz');
// zzz
// aaa
// bbb
// ==UserScript==
// @name Docbase link replace
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://your-team.docbase.io/posts/*/edit
// @icon https://www.google.com/s2/favicons?domain=docbase.io
// @grant none
// ==/UserScript==
(function() {
'use strict';
let objectToString = Object.prototype.toString;
const wait = (sec) => {
return new Promise((resolve, reject) => {
const intervalId = setInterval(()=> {
const element = document.querySelector('.CodeMirror-codey');
if (element !== null) {
clearInterval(intervalId);
resolve(element);
}
}, sec*1000);
});
};
async function main() {
try {
const codearea = await wait(1);
console.log(codearea.innerHTML);
} catch (err) {
console.error(err);
}
}
main();
})();
補足(修正点・秘匿情報):
@nameが「Esa link replace」となっていましたが、実際の@match対象は esa.io ではなく docbase.io だったため名称を修正しました。また@matchに含まれていた個人(または特定組織)の docbase サブドメイン(h-sawai.docbase.io)は秘匿情報とみなし、汎用的なプレースホルダー(your-team.docbase.io)に置き換えています。