#はじめに
過去にブックマークレットでjQuery使ってajaxをする記事を書きましたが、今時はjQueryを引っ張ってくることもなく標準装備のfetch
をするのが主流っぽいので、ブックマークレットからfetch
するサンプルです。
#今回のブックマークレット
みんな大好き楽天市場の検索結果から、リンクを探すブックマークレットです。rakuten.co.jpを開いたブラウザで実行してください。
##普通のコード
普通に書くとこんな感じになります。
javascript:(
function(){
let URL = "https://search.rakuten.co.jp/search/mall/%E6%9C%AC/";
fetch(URL)
.then((response)=>response.text())
.then((html)=>{
//HTMLからリンク探す部分
let regexp = /<a target="_top" href="([^"]*)" data-track-action="img">/g;
let match;
let matches = [];
while ((match = regexp.exec(html))!== null) {
matches.push(match[1]);
console.log(match);
}
//リンクを使って楽しむ部分
console.log(matches);
});
}
)();
無事、fetch
できました。
jQueryのajax
関数と同じでfetch
は非同期な関数なのでコードがネストします。実際にやりたいことを書く部分がインデントの奥に閉じ込められますね。
##fetchの非・非同期化
fetchが非同期なら、非同期をやめてしまいましょう。ブックマークレットですし、ちょっとくらい待たせしたところで、困るのは自分だけですからね。非同期を強制的に辞めることは何と呼べばいいのでしょう?同期化というのも変なので、非・非同期化と呼んでおきます。
javascript:(
async function(){
let URL = "https://search.rakuten.co.jp/search/mall/%E6%9C%AC/";
let links = await getLinks(URL);
//リンクを楽しむ部分
console.log(links);
//HTMLを取りに行ってリンクを探す部分
async function getLinks(url){
let response = await fetch(url);
let html = await response.text();
let regexp = /<a target="_top" href="([^"]*)" data-track-action="img">/g;
let match;
let matches = [];
while ((match = regexp.exec(html))!== null) {
matches.push(match[1]);
}
return matches;
}
}
)();
変更点は、
- Linkを取り出すところをgetLinksに関数化し
- 関数内の
fetch
の呼び出しにawait
を付け - その下の
response.text()
にもawait
を付け - getLinks関数を
async
で宣言をする。 - getLinksの呼び出しにも
await
を付け、 - 無名関数を
async
にする
かなり読みやすくになりました。関数を実行したらリンクが返ってくるという構造は、理解しやすいですね。
注意点は、
awaitした後に取れたresponse
を操作するからresponse.text()
は普通に呼び出せそうな気がしますが、await
しないと失敗します。
getLinsk
自体がasync function
なので、呼び出すときにawait getLinks()
にしないと結果を待たずに処理が進んでしまいます。
getLinks()の呼び出しをawaitにした関係で、先頭にある無名関数もasync
にしないといけません。
最後のやつはハマりました。
まとめると、こういうことです
- asyncな関数(fetch, getLinks)は、awaitすると待てる。awaitしないと止まらずに進んでしまう
- awaitを1つでも書いた自作の関数(getLinks, 先頭の無名関数)は、asyncを付けないと構文エラーになる
##Closure Compierのオプション設定
上のコードをClosure Compilerでコードをコンパイルすると、短くなるどころか長くなります。async, await, for-of など新しい文法ったスクリプトを、古いブラウザでも動くようにコンパイラが変換してくれます。余計なお世話ですね。
コードが長くなってもちゃんと動きますが、ブックマークレットのコードは短いほうがいいです。回避するためにClosure Compiler の設定を足す必要があります。
407バイトのコードが13169バイトに増幅!
###ECMASCRIPT_2017 でCompileする
Copmpiler のオプションに(左側のjavascriptのコードを書く部分の先頭にあるコメント部分)に@language_out ECMASCRIPT_2017
を追加します。Closure Compiler WebのUI上では設定できないのですが、書いちゃえばいいらしいです。UIで入力可能にしてくれればいいのに。。
無事、短くなりました。
Closure Compiler(Web)に貼るときに毎回書くことになるので、コードの先頭に書いちゃいましょう。javascriptのコメント形式なので動作には影響ないです。
ということで、最終形はこちらです。
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// @language_out ECMASCRIPT_2017
// ==/ClosureCompiler==
javascript:(
async function(){
let URL = "https://search.rakuten.co.jp/search/mall/%E6%9C%AC/";
let links = await getLinks(URL);
//取ってきたリンクを楽しむ部分
console.log('show link (%s)', links.length);
links.forEach(function(v,i,ar){
console.log('[%s] %s', i, v);
});
console.log('END OF SCRIPT');
//HTMLを取りに行ってリンクを探す部分
async function getLinks(url){
console.log('start getLinks()');
//fetchの結果を待つ
console.log(' start fetch');
let response = await fetch(url);
console.log(' end fetch');
console.log(response);
//response.text()もawaitが必要
console.log(' start response.text');
let html = await response.text();
console.log(' end response.text');
console.log(html);
let regexp = /<a target="_top" href="([^"]*)" data-track-action="img">/g;
let match;
let matches = [];
while ((match = regexp.exec(html))!== null) {
matches.push(match[1]);
}
console.log('mathced %s', matches.length);
console.log('End getLinks()');
return matches;
}
}
)();
#参考資料
https://developers.google.com/closure/compiler/docs/api-ref
defaultだとECMASCRIPT5なのですね。
https://github.com/google/closure-compiler-js/blob/master/README.md#flags
フラグの種類
https://github.com/google/closure-compiler/issues/3086
Closure Compiler Webでもlanguage_outputオプションを使えることを知ったチケット。$jscomp is not defined
が出た場合もたぶんオプションの設定で回避可能です。