Google Apps ScriptのV8 Runtime対応を検証してみた【GAS】
2020年2月6日、ついにGoogle Apps ScriptのJavaScript基盤でV8エンジンが利用可能になりました。これまでのGASの実行基盤はES2015ベースとは言え、一部が使えていただけで、最新のJavaScript環境で利用可能になっている構文は使えない状態にあったので、これでGASの書き方が一気に変わってくるかもしれない。
これまではMozillaのRhinoというエンジンで動いていたようで。現在はまだ選択式なので、V8にするためには設定が必要だけれど、いずれは現在のエンジンは廃止される見通しなので、今のうちにV8での書き方に慣れておく必要はあるかもしれない。今回はこのV8でどういう構文が使えるようになったのか?一個ずつ検証すると共に、GASの実行スピードも検証してみたい。
※注意が必要なのが、GASはデフォルトでV8がONになってしまってます。。。新規作成時に要注意です
目次 [隠す]
- 1 今回使用するスプレッドシート
- 2 有効にする手順
- 3 V8対応検証
- 3.1 最新のECMAScript仕様のメソッド
- 3.2 letとconst
- 3.3 getYear()の仕様変更
- 3.4 onOpenのメニューでオブジェクトメソッド呼び出し
- 3.5 アロー関数
- 3.6 Class構文
- 3.7 分割代入
- 3.8 テンプレートリテラル
- 3.9 関数のデフォルト引数
- 3.10 複数行に渡る文字列
- 3.11 Promiseで順番に処理
- 3.12 イテレータ
- 3.13 ジェネレータ
- 3.14 for eachの変更
- 3.15 Symbol
- 3.16 Setオブジェクト
- 3.17 オブジェクトを配列で返す
- 3.18 配列の検索
- 3.19 累乗計算
- 3.20 スプレッド構文
- 3.21 整数だけ取り出す
- 3.22 正規表現
- 3.23 Mapオブジェクト
- 3.24 String Padding
- 3.25 関数の引数最後にカンマ
- 3.26 日付の出力形式の変更
- 3.27 undefinedな値をsetValueすると
- 3.28 ファイルの順番に意味がある
- 3.29 ライブラリを使う場合の注意点
- 3.30 予約語に注意
- 3.31 Xml.parseは使えません
- 3.32 その他
- 4 バグ?やトラブル
- 5 スピード検証
- 6 関連リンク
今回使用するスプレッドシート
今回のスプレッドシートのappsscript.jsonには、runtimeVersionとしてV8を指定しています。
※Logger.logの結果が出るのがなんだか遅い。App Scriptダッシュボードのほうは割とすぐ出る。
有効にする手順
スクリプトエディターの場合
今回のV8 Runtimeを使うには以下の手順が必要です。本来はスクリプトエディタの「実行」の中に「Chrome V8を搭載した新しいApps Scriptランタイムを有効にする」というものをクリックして有効にすればそのスクリプト内で有効になります。まだ、表示されていない人は手動で変更も可能。無効にするをクリックをすれば元に戻ります。
図:メニューから簡単に切り替えられるよ
手動で変更する場合は以下の手順。詳細はmanifest structureを参照してください。
- スクリプトエディタを開く
- メニューの「表示」⇒「マニフェストファイルを表示」をクリック
- appsscript.jsonを開き、1行以下のようなものをを追加する
- 保存すれば、すぐに使えるようになります。
- もとに戻す場合には、この行を削除すればオッケーです。(DEPRECATED_ES5を指定しても可)
図:簡単に使えるようになります
claspの場合
Google Apps Scriptは、Node.js上のTypescript開発環境Claspでも開発ができます。clasp上でもV8 Runtimeが動かせるようになっています。手順としては以下の通り。こちらを参考にclaspをES2019に対応させる必要があります。
- clasp createで新しいプロジェクトを作成する
- 生成されたappscript.jsonを編集する
- 以下のようにコードに追記する
- また、typescriptを使う場合にはtsconfig.jsonも編集しておく
図:appscript.jsonの編集箇所
図:tsconfig.jsonの編集箇所
基本、スクリプトエディターの時と変わらないですね。
V8対応検証
V8対応といっても、現時点でES6 Moduleのimport, exportにはまだ対応していないとの事なので、それ以外のものについて検証をしてみたい。
もはや、Google Apps Scriptというより、Google Apps Script + JavaScriptのサーバレス実行環境になったという印象。これは初心者の人にとっても非常に有用で、GASの流儀だけに縛られずに最新のJavaScriptのノウハウやメソッドをそのまま持ち込めるので、楽になります。ES2023までの数々のものに多く対応しています。本エントリーはES2019までのメソッドについて調査しています。
最新のECMAScript仕様のメソッド
ES2017の仕様に準拠した環境をリリースって話だったので、使えないよねって思って、ES2019で追加されたと言ういくつかのコードも試してみてる。その内の1つ、配列データをフラットにするflat()、flatMap()を使ってみたら、使えました。ふむふむ。クライアント側で動かすわけじゃないから、割と最新のメソッド遠慮なく入れてきてる感じですかね。
2024年6月現在、試してみたところECMAScript2023まで対応を確認しました。相変わらずES6のModuleには未対応ではあるものの、公式でも最新のECMAScriptに対応を謳ってる。このV8リリース当時はES2019まで対応でしたが、やはりChromeのアプデと共にバージョンアップしていくようです。とはいえ、Class構文についてはStatic変数やPrivate Propertyには未対応なのでES2023対応と言っても全てに対応してるわけじゃなさそうです。
配列同士でキーのペアを作ってオブジェクトを生成するObject.fromEntriesなども使えました。これは意外と便利かもしれない。
ES2015に加わっていたtrimに加え、trimStartやtrimEndも使えるようになっていました。
letとconst
これまでのGoogle Apps Scriptは変数の宣言は、PrivateもGlobalも「var」で宣言する事しかできませんでした。そのため、宣言した変数はもちろん、書き換える事ができるので、定数を宣言できないが為に面倒臭いことになったりしました。また、varはJavaScriptの巻き上げという仕様によって、頭で宣言しようが後で宣言しようが、頭で宣言したものとみなされる仕組み。
今回より、定数であるconstが利用できるようになり、またブロック内(例:For文の中のみとか)でだけ参照変数letも使えるようになり、変数何入っていたっけ?みたいな事が減ります。
※以前は、const自体あったものの、初期値を入れたあとに再代入しようとしてもエラーにならず、初期値が表示されるというオカシナ挙動でした。
※constはグローバル定数として宣言は出来ません。グローバルはvarで宣言し、各関数内でconstに代入するしかありません。
- 同一ブロック内でletで同じ変数を宣言するとSyntaxError: Identifier 'price' has already been declaredとなりエラーとなる。
- ブロック外でletを参照すると、ReferenceError: price is not definedとなりエラーとなる。
- constに対して、値を再代入しようとすると、TypeError: Assignment to constant variable.となりエラーとなる。
グローバルでconstを宣言するだけでなく、関数内でもconstは宣言可能。但し、letと同じくブロック内で有効となるので、関数内で使うなら、頭のほうで宣言しておくほうが迷わなくて済みます。
getYear()の仕様変更
古いRhinoの場合、1999年より前の1900年までは、getYear()では、2桁の年を返し、getFullYear()で4桁の年を返す仕様でした。また、2000年以降はgetFullYear()では、もちろん4桁の年を返してくれます。
しかし、V8からはgetFullYear()については同じく4桁で返すようになっているのですが、問題は2020/2/1をgetYear()で取得すると120が返ってくる・・・これは年から1900を引いた数が出てしまうので、もし古いコードでgetYearを使っている場合、大きくご動作する可能性があります。
V8ではgetYear()を使わず常に、getFullYear()を使うよう推奨されています。
図:2000年以降のgetYear()は挙動が変わってるので要注意
onOpenのメニューでオブジェクトメソッド呼び出し
これまでのカスタムメニューを作るui.createMenuでは、addItemで呼び出せるのは単一の関数のみでした。しかし、V8からは、クラスが使えるようになったこともあって、オブジェクトメソッド形式のものも呼び出せるようになりました。オブジェクトメソッドとは、オブジェクト名menuの中に複数のメソッドがある場合、menu.item1やmenu.item2といった形で関数を呼び出すものです。(SpreadsheetだけじゃなくSlideやFormでも同様です)。
アロー関数
アロー関数はこれまでの、function taxcalc(){}といった記述ではなく、=>といった矢印で処理を記述するような感じになります。もちろん、これまでの記法も利用可能。{}を使わない程度のものなら、1文で書くことも可能。引数がない場合には()だけでOKとなります。
前述のtaxcalc関数をアロー関数で書くとこうなります。
Class構文
これまでのJavaScriptことGoogle Apps Scriptでは、様々な関数を用意してそれを呼び出すようなシンプルな構造でした。今回のアップデートによって、Google Apps ScriptにてClassが使えるようになり、Javaに代表されるようなオブジェクト指向型言語のようにコードを記述する事が可能になります。
単発のコードだけで見ると、別に関数でいいじゃないかと思いがちですが、Classというものは単なる引数与えて答えもらうだけのものではなく、いくつものメソッド、プロパティを持って様々な処理を総合的に引き受けてくれるものなので、コードの再利用性が高まります。また、その内容も実に深いので、クラスは嫌いという人もいたりします。クラスの理解はこちらのサイトがとても参考になります。なかなか最初はサッと使えないかもしれませんが、身につけたら大きな武器になります。
※Class構文だけ別エントリーで作成しました。そちらで詳しく解説しています。
- taxcalc3には3つのメソッドが用意してあります。メインのcalcret、getterであるzeikin, staticなtakasugi。
- 使う側は、newでtaxcalc3インスタンスを用意して、その中のcalcretを呼び出し、引数を与えて処理をしてもらう
- staticはnewでインスタンスを用意せずとも、taxcalc3.takasugi()でいきなり呼び出せます。
- getterだけでなく、プロパティに値をセットするsetterもあります。
- 他にもextendsな継承、superなオーバーライド、プロパティの直接参照を防ぐSymbolなど覚える事山のごとしです。
- Javaなどではおなじみの継承して、新しいクラスを用意してなんてやり方が、Google Apps Scriptでも出来るようになるわけですね。
- ret.zeikinでgetterであるzeikinメソッドを実行し、単品の税率がLogger.logに残るようにしています。
分割代入
ある配列に何個もある数値をそれぞれの変数に一個ずつ取り出すようなコード。これをスパッとまとめて用意した変数に一発で代入するのが、分割代入です。スプレッドシートの範囲のデータを取得した時に、1レコードの各列の値を使って何かする時に、効果を発揮しますね。
- PriceとMarginの2つの変数に1次元配列の値をぶっこんでいます。
- 今回は2個だけですが、これが結構な量ある場合には、非常に有用です。
テンプレートリテラル
これまでは、例えばUrlfetchAppなどで変数を取得し、URLを組み立てるような場合には、+演算子などを利用して組み立てていたと思います。テンプレートリテラルを使う事で、これをもうちょっと簡単に組み立てられます。Vue.jsなどでは結構見かけるような記法ですね。
ただ、まだGAS側のコードの認識がおかしいのか、正しい記法なのに、https:の部分だけ黒文字という状態です。またこの時URLをくくっているのは「シングルコーテーション」ではなく「アクサングラーブ(バッククオート)」という記号になるので、シングルコーテーションだとエラーになります。似てるけれど違います。記号と読みを参照してみてください。入力方法は、Shift+@で入力できます。
- アクサングラーブ(バッククオート)でURLを括っています。
- ${key}で変数keyを取得しています。
- ${sheetID}で変数sheetIDを取得しています。
関数のデフォルト引数
これまでのGoogle Apps Scriptでは、functionで関数を作った場合、デフォルトの引数は存在しません。故にundefinedになります。そのため、引数指定がなかった場合の処理を用意しておく必要がありました。しかし、関数のデフォルト引数を指定出来るようになったので、指定がない場合には、既定値を持って処理する事が可能です。
- omikuji関数の既定値は1でセットしてあります。
- 呼び出す時、引数なしでomikujiを引くと、大凶が返ってきます。
- 複数引数に規定値をセットする場合には、function omikuji(param1=1,param2="てんびん座"){}といったような指定になります。
- 引数にundefinedを指定した場合には、引数は1になります。
- 引数で演算させた結果を利用、例えば第一引数が1で第二引数が2, 第三引数では、1 + 2の結果を使うといったような書き方も可能になっています。
複数行に渡る文字列
これまでのGoogle Apps Scriptでは、改行をするような形での文字列の場合には、行末に「\n」という改行コードをつけ、各行はコーテーションでくくる。そしてそれぞれの行を+演算子で結合して、、、なんてやって、複数行に渡る文字列を作っていました。改行コードがなければ、例えばダイアログの中で改行されず、1行に全部つながった文字になってしまうわけです。
しかし、今回のアプデではこれも「アクサングラーブ(バッククオート)」記号を持ってくくれば、見ているまま適当に改行すれば改行して表示してくれるようになります。コーテーションで括るとエラーになりますので、注意。
図:いちいち改行コードとか鬱陶しいのを入れなくて良い
Promiseで順番に処理
Google Apps Scriptは基本的には同期処理なのですが、時々非同期にパーンって処理が進んじゃって、なんだか思ってた答えと違うものが返ってくるシーンがあります。Node.jsのように非同期が当たり前の処理だとCallbackを利用したり、Promiseで順番に処理をしたり・・・
そんなPromiseも使えるようになってるみたい。
- HTML Serviceのgoogle.script.runは非同期実行なので、順番に実行させるにはPromiseが必須です(元からHTML側はPromiseが使えます)
- Promise.allで順番に実行が可能です。他のPromise記法も大丈夫。
- async/awaitの記法も問題なく実行できました。
図:totocoの雲丹醤油美味しいです
また、複数のresolveを利用して確実に同期処理をするには以下のようなコードを使うと良いでしょう(特にgoogle.script.runなどを使うケースでは)
- 上記の例だと、exportlogとtransferlogの2つのGAS側関数を順番に実行しています
- 本来、google.script.runは非同期で実行されてしまうので、並列して書くと実行順番が保証されませんが、Promise.resolveを使う事で、期待通りの処理になります。
イテレータ
Google Apps ScriptでもDriveAppなどでファイルのリストを取得する際に利用されているものですね。配列のようなものに対して繰り返し作業をして値をどうこうする仕組みなので、配列もイテレータのオブジェクトと言えます。
これをスプレッドシートの処理などで使うとこれまでは、二次元配列で取得されたシートのデータを取り出すのに、for文とカウンタを利用して、取り出していたものが、for-ofという今回より使えるようになったものと組み合わせると簡単に取得が可能になります。カウンタのlengthとかも気にする必要がないので、Goodですね。
- シートデータをobjに全部入れておく
- イテレータを用意して、イテレータを回す。すると、vに1行文のデータが入ってくる
- 現状は、asyncと組み合わせて非同期イテレータみたいなことはできないようです。
ジェネレータ
イテレータと異なりジェネレータは理屈を理解するのに少し時間がかかります。この関数は、呼び出されてもその全てを実行してから返す事がなく、呼び出す毎にあらかじめ設定しておいた値を返す仕組みです。よって、連続で呼び出しても毎回答えが異なる。
特徴的なのは、yieldで必ず止まり、値を返す。二回目は次のyieldで止まって値を返すを繰り返してくれます。最終的に設定してるyieldの数を上回る回数回した場合には、doneがtrueという形で返ってくるという変わった関数です。呼び出される関数側はfunction*という形でアスタリスクが付くのも特徴です。
- ジェネレータを回す場合には、.nextで次の回を回せます。
- ジェネレータに引数を渡すことも可能です。
for eachの変更
地味ながら、ここは修正が必要になる項目です。これまでもGoogle Apps Scriptでfor eachが利用できましたが、V8からは記法が変わります。eachを削るだけです。Rhino使おうとするとSyntaxError: Unexpected identifierとして弾かれる。
図:能力値のオブジェクトから値を取り出してみた
Symbol
Symbolとは何か?唯一無二のuuidのようなものを生成する関数で、かといって生成されるのは文字列じゃないので、console.logしても文字列は出てこない。これも以前のGoogle Apps Scriptでは使えず、使おうとするとReferenceError: 「Symbol」が定義されていません。というエラーが出ます。型名もsymbol型という・・・
また、このSymbolはオブジェクトのプロパティのキーとしても使えるという事なので、連想配列な{}の中に於いて、key名となることができる。二度同じ値は生成されないということで、絶対に被らない変数というものを用意等に使ったりするけれど、文字列じゃないので、uuidのような感じでは利用できない。
個人で使う機会あるかなぁというと、あまり無いかもしれない。チームで開発してる場合、同じようなモジュールでプロパティ名も同じ、けれどちょっと違う挙動なんて時に、参照する名前が同じで困るなんて事を避けられるくらいかなぁ。
Setオブジェクト
配列のような集合体であるSetオブジェクトが使えるようになりました。同じデータを重複して塊に含めることは出来ない仕様。データの追加はaddで行い、存在確認はhasで行う。deleteで削除ができ、sizeで個数を調べることができる。配列のようにインデックスを指定して値を取ることは出来ない。clearをすると空っぽになる。
値の取得はループなどで取り出すことが可能(追加順で出てくる)。
オブジェクトを配列で返す
ES2017で規定されている、オブジェクトの各値を配列で返す、もしくは二次元配列で返すObject.valuesおよびObject.entriesが使えるようになりました。これによって、例えばJSONファイルを取得した時に、中に入ってる値を配列に突っ込みたい時、ループでどうこうしなくて済みます。
配列の検索
これまで、Google Apps Scriptでの配列の検索は、indexOfを使ったりループで回してチェックしてみたりしてました。今回のアップデートより、includesメソッドが使えるようになったので、存在確認や末尾から探すなんてことも可能になりました。
累乗計算
実はいままでのGoogle Apps Scriptでは、累乗はMath.pow(2,8)といったメソッドを使っていました。ループを使って自前で作っていた人もいるみたいです。あまり使う機会はないかもしれませんが、以下のような簡単な構文で計算結果が出せるようになりました。
スプレッド構文
配列データの中身をconcatのようにつなげたり、足したりできる構文です。他の関数と組み合わせて、配列データを食わせて色々よしなにできます。この時使うのが「...」この点々。以下の関数では、101が答えとして返ってくる。
それだけじゃなく、複製したり、Array.prototype.push.applyのように配列をどんどんpushしたり、文字列を配列にしてみたりと、色々と加工ができる。
※ES2018で修正されたオブジェクトに対してのスプレッド構文も問題なく動きました。
図:オブジェクトに対してスプレッド構文で入れてみた
整数だけ取り出す
結構Math関係はGoogle Apps Scriptは使えていたのですが、それでも新しいメソッドは未対応だったので、自前で作ってたなんてケースは多いはず。そんな一つにMath.trunc。小数点の値をぶちこむと、整数部分だけ取り出してくれる。こんな感じの地味なんだけれど、すごく助かるものが、使えるようになった事が今回の対応の一番の利点なのではないかと思う。
正規表現
これまでもGoogle Apps Scriptでは正規表現が使えていたわけですが、ES2018で追加されているsフラグも使えるようになりました。「 . が完全にすべてのワードにmatchするようになる」ものです。特殊な改行コードやユニコード記号(U+2029など)も対象になるわけでっす。
他にもES2018で強化された正規表現の機能(後読みアサーション、replace()との併用、
Mapオブジェクト
ES2015より追加されたMapオブジェクトが利用可能になっています。配列の処理のパターンがこれでまた1つ加わりますね。キーと値の組み合わせを持った配列等の処理をする場合に効果があります。
反復処理はforeachに似たようなスタイルです。早速、Mapオブジェクトを利用したクラスを作られた方がいる。JSONオブジェクトの扱いが楽になるなぁ。
String Padding
String Paddingとは規定の桁数や文字数があり、それに満たない文字の場合には、指定の文字で埋めてあげるもの。具体的にいうと10桁の商品コードがあって、5桁文しかないコードの場合、10桁になるように頭に0で埋めるといったことがメソッドで可能になります。社内アプリとかだと結構使うシーンがありますね。
padStartで前埋め、padEndで後ろ埋めになります。
関数の引数最後にカンマ
よくやってしまうのですが、functionの引数に複数の引数を取る時、2つしか入れず、最後にカンマ(通称ケツカンマ)をつけたままにしてしまうことがあります。Google Apps Scriptでこれをやると、仮パラメータがありません。と怒られます。V8エンジンではこれに対してエラーがでなくなるようになります。
日付の出力形式の変更
これまでの日付形式はnew Date()した値は、toLocaleDateStringですと「December 21, 2012」みたいな表記でした。これが今回からは「12/21/2012」となり、また、引数指定でja-JPなどを指定すると、2012年12月21日で返ってくるようになりました。
undefinedな値をsetValueすると
これまでスプレッドシートなどで指定のセルにsetValueする際に、引数の値がundefinedな時には、undefinedという文字がセルに入ってしまっていました。しかし、V8エンジンからは、null値が入るようになります。
ファイルの順番に意味がある
スクリプトエディタの場合
古いGoogle Apps Scriptでは問題なく実行できたのに、V8からは実行すると「ReferenceError: xxxx is not defined」と出て、なぜかエラーになるようになりました。これは、V8エンジンの特性で、スクリプトエディタの左パネルに於いてファイルが並んでいますが、上から順番に参照されるので、参照された次のファイルに関数がある場合、まだ読み込んでない関数を呼び出そうとすることで発生します。
特にこれはグローバル変数で引き起こされます。
上記の場合、v8test関数を実行するとエラーになります。実行時v8testの前にranran関数が読み込まれていないからです。同じ、test1.gs内であれば、v8testの下に記述しても問題なく実行されます。そのため、同じ関数名の関数が複数どこかに入っている場合、最後に読み込まれたものが有効になるようです。
故に左サイドに関数を作り込んでいく上で、上から順番に読み込まれて実行されるという事を意識する必要性があります。また、同じファイル内ならば順番は意識する必要はありません。
※ならば、左サイドパネルのファイルの位置、好きなように上下できるようにしてほしいものだ。
claspの場合
V8状態でclaspでGAS側へpushする場合は少し様相が異なるようです。なんの指定もなく今まで通り関数やクラスを作って、pushした場合、ファイルの順番が「アルファベット順」で並ぶことになり、場合によっては他のファイルを参照してる場合、同様にエラーが発生する(つまり、Axxx.gsとBxxx.gsがあった場合、BのクラスをAが参照してもまだBは読み込まれていないことになる)
エディタのリスト順よりも優先してしまうという事ですね。
これを正す為には、clasp.jsonファイルの中に以下のようなfilePushOrder項目で順番にpushさせるよう指示を加えて上げる必要があるようです。
bxxx.jsからまず上げて、次にaxxx.jsをあげる。こうすることで、bxxx.jsがエディタ上のリストで言えば先に出てくるようになるので、bxxx.jsのクラスを無事にaxxx.jsが参照できるようになるという訳です。
※コメントで情報いただきました。大変ありがとうございます。
ライブラリを使う場合の注意点
GASのライブラリを使う場合、呼び出し元のプロジェクトがRhinoで、呼び出し先のライブラリ側がV8の場合、呼び出し元でそのライブラリを使った場合、V8でなければ使えないメソッド等の場合エラーとなります(逆にV8では使えないメソッドをライブラリ側で使っていた場合も同じ)。
呼び出された側のエンジンで動く事になるので、ライブラリ運用してる人は要注意事項です。必ず、両方のエンジンは同じにする事。ライブラリ側でうまく動いてるから問題なしと思ったら、それを使っていたプロジェクト側がV8ではなくエラーがバンバン出ることになります。
図:安易にライブラリ側はV8にすると厄介な事に
予約語に注意
これまでのGoogle Apps Scriptは予約語については緩い感じだったようですが、V8からは予約語が厳しい。予約語になってるワードを変数名や関数名などに使わないように気をつけましょう。
しかし、そのほとんどは、メソッド名である「if」や、return, varといったワードの類。ここにenum、package、interface、さらには型の名前などが入っているので、およそ変な事をしなければ予約語にぶつかるということはないんじゃないかなぁと。ただ、importやexportなどこれまで使えていたものは、使えなくなるようなものが出てきているので、ここは要注意です。
Xml.parseは使えません
地味に大きいのが、XMLデータのパースをする為のXml.parseが使えなくなったことです。RSSデータなどを処理するのにこれまでXml.parseやnew XML(要素)を使っていたようなコードは、全てXmlService.parseを利用する必要性があります。ただ、Namespaceを指定して取り出すだとかは、結構な面倒な作業なので、こちらのエントリーを参考にして、JSONに変換して取り出すほうがスマートかもしれません。
XML取り出し例としては以下のような感じになります。dc:identifierみたいなコロンを含んだものは、namespace指定をしないと取り出せないので、色々頑張る必要があります。
その他
ES2017から加わったメソッドなどが普通に動くようになっています。Polyfillとかも対応しているのかも(サーバサイドだから意味ないけれど)。一方で、Object.prototype.toSource()といった古いメソッドが廃止されていたりもします。
バグ?やトラブル
V8ランタイムにする事で、Rhinoの時のコードの一部に不具合が発生する可能性は十分あります。またエンジンを変更している為、そもそも過去に廃止になってるものは、Rhino上では互換性のために維持していても、V8ではスッパリ切られているケースも。
- 2019年8月廃止されたFusion Tableのサービスは、コードを残しておくとエラーになります。UiAppなどのコード類も同義。
- ライブラリ側でもV8対応していないようなケースでは、エラーになるケースがある模様(Twitterライブラリで報告あり)
- HTML ServiceのcreateTemplateFromFileにて、HTML側でoutput.appendを利用している場合、TypeError: output.append is not a functionが出るバグが出ています。V8 Runtimeでoutputを使いたい場合は、「output._=('<b>test</b>');」を利用するとエラーになりません。
- V8にてJDBCサービスを利用するとReferenceError: Jdbc is not definedというエラーが出る報告があります。
- CSVを取り込む為のUtilities.parseCsvにてエラーが出る報告が上がっています。遭遇してしまったら、CSVToArrayを利用してみてください。stackoverflowでV8でも動く対応版コードも掲示されています。
- new Date()で生成した日付について、Logger.logに残る日付と表示する日付に時差分の誤差が生じているエラー報告あり
- ドキュメントにアクセスするようなWeb Appの場合、Exception: Document ****** is missingといったエラーが出て、Rhinoに戻してもアクセス出来ないケースがある(アクセス権はきちんと適用されているので、改めてGASを作り直すと動くパターン)
- claspでv8コードをpushするとエラーが出ている。
- Logger.log等のログ出力の反映が異常に遅い。5〜6秒後に遅れて出力されている現象(しかも、遅れて一発で出ずに、何度か表示しないとすべて出ないケースがある)。console.logで出力し、ダッシュボード側で確認しましょう。
- バグじゃないけれど、2月に入ってから、UrlfetchAppにてGoogle DriveのファイルURLを取得しようとすると、403エラーが返り拒否されるようになったみたい。
- HtmlService.getUserAgent()にて、ユーザエージェントが取得できずnullが返ってくる。Rhinoだと「Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36,gzip(gfe),gzip(gfe)」といったユーザエージェントが帰ってくる=> 多分この影響かも。
- Googleスプレッドシートに於いて、onEditトリガーを設置、toastを表示するコードが、オーナー以外が編集した時に表示されないバグが発生しています。
- 拡張サービスであるDrive Activity APIを有効にしてDriveActivity.primaryActionDetailを取得すると、余計な情報まで全部ついてくるバグが発生しているようです。
- 動画ファイルからサムネイル画像を生成するgetThumbnail()が正しく動作せず、サービスエラーとなる。V8にすると一見関係ないメソッドでもGAS特有のメソッドに関しては致命的バグを抱えているみたい。
- また、同じエントリーにして画像ファイルをBlob⇒Base64⇒Blobで比較した際に、V8とRhinoとでエンコード結果が異なるというバグが報告されています。V8だとなぜかどんなファイルでも/9j/4A==が結果として返ってくる・・・以外と結構使うメソッドなので、画像関係やファイル関係のBlob取得系ではV8はしばらく避けたほうが良いかも。
- google.script.run.withSuccessHandler(OnSuccess).exefunction()にて、アップロード画面を制作した場合に、呼び出す関数であるexefunctionが実行されない致命的な問題があります。こちらでも同様なものが。ウェブアプリケーションを作る上では、V8はまだ利用は早い!!といった印象。
- よく利用されているOAuth2認証用ライブラリですが、現在はV8ではエラーが発生します。V8を無効にして利用するか?自前で認証をするためのコードを書いて回避しましょう。(OAuth2認証をするサンプル)
- GMail Addon用コードにてV8の場合、CardServiceにてthrowExeptionにてエラーが発生するようになります。
- 一部のケースで単純なMailApp.sendEmailを使ったメール送信スクリプトが、スクリプトトリガーで発動した場合に動かないケースが報告されています。
- 古いGoogle Sitesに埋め込みのGoogle Apps ScriptではV8は利用できない。This action is not supported unless the runtimeVersion is set to DEPRECATED_ES5 in the appsscript.json file.といったメッセージが出たりします。DEPRECATED_ES5の指定が必要です。ただ旧サイトは既にDeprecated対象なので、早く新しいGoogle Sitesに移行すべきでしょう。
- MailAppに於いて、宛先を複数指定する場合、配列で指定するのですが、V8ですとこれが「number[],String,String,(class))が MailApp.sendEmail のメソッドのシグネチャと一致しません。」とエラーが出て送信出来ない。代替策としてGmailAppを利用すると、こちらは送信出来ました。GmailAppは添付ファイルの指定やパラメータの指定がちょっと違うので注意。
- ScriptApp.getService().getUrl()にて、WebアプリのURLが取得出来るのですが、V8だと/dev側のURLが取得され、ES5だと/exec側のURLが取得される状態。こちらで報告されています。こちらの回避方法についてはこちらのエントリーでまとめてみました。
- HTML Service上で作ったアップローダからバイナリファイルをアップロードしようとすると、Driveに生成時にファイルが破損する。V8オフであれば正常に動作します。
コード:同じ結果にならないバグ
図:まだまだバグがたくさん残っているようです
コード:Utilities.parseCSVのバグ対応版コード
コード:無関係のように見えるメソッドでも動作しないケースが
コード:Blob操作なのかBase64デコードがおかしいのか・・・
スピード検証
以前高速化のエントリーで作成したテスト用スプレッドシートをV8にして、テストをしてみました。わかりやすいように「最悪なコード」で書いた場合と、「一番良い事例」で書いたコードでベンチマークしてみます。
このシートは1000行のデータの中から、1000というIDを持ったレコードを上から順番に探していき、見つけたらタイムを出すという仕様です。3回計測して一番良い値を取りました。
以前のGoogle Apps Scriptの場合
最悪なコードで書いた場合の検証結果は、「119.756(秒)」でした。
一番良い事例で書いた場合の検証結果は、「0.466(秒)」でした。
最悪なコードは無闇矢鱈にGASのAPI呼び出してるので、ものすごくオーバーヘッド掛かっているのに対して、一番良いコードは配列で取得して探索してるので、あっという間に終わります。
V8 Runtimeの場合
つづけて、同じデータで同じコードで、V8エンジンの場合の検証です。
最悪なコードで書いた場合の検証結果は、「97.078(秒)」でした。(18%ほど早かった)
一番良い事例で書いた場合の検証結果は、「0.424(秒)」でした。(19%ほど早かった)
最悪なコードで書いた場合も、一番良い事例で書いた場合も、いずれも18%程度高速化されているように思えます。
関連リンク
- JavaScript アロー関数を説明するよ
- ES6の新機能: 「let」「const」宣言を調べてみた
- 【JavaScript入門】class構文の使い方・書き方が分かるようになる方法!
- 《JavaScript》ES6の分割代入のはじめかた。
- JavaScript の テンプレートリテラル を極める!
- JavaScript 関数のデフォルト引数のサンプル
- JavaScriptで複数行の文字列や変数を扱うならテンプレート文字列が便利
- PromiseによるJavaScript非同期処理レシピ集
- ES2015(ECMAScript2015)の文法を覚える
- 【GoogleAppsScript】ES6を使ったGoogle Apps Scriptの開発
- V8エンジンでのJavaScriptの機能と最適化コードの書き方に関する5つのベストプラクティス
- V8の基本的なAPIを学ぶ
- JSエンジン「V8」はバージョン6で世代移行を終える ── Google I/O 2017レポート
- Google Apps Scriptで使えるJSのバージョンについて調査
- Google Apps Script で実行できる繰り返し構文について
- Apps Script runtime powered by Chrome V8
- V8 Engine roll-out over the next few days…
- ES2015(ES6) 入門
- Google Apps Script Development 💯
- V8対応なのでクラス書いてみてハマった点
- Promiseについて0から勉強してみた
- What is V8?
ファイルの順番ですが、Claspを使ってる場合、.clasp.jsonのfilePushOrderで指定した順になるようです。
上野さん
追加情報ありがとうございます。ブログにも追記しておこうと思います。
ファイルの指定順ですが、
・派生クラスの場合は基底クラスの宣言が先のファイルでなければならない
・クラスのインスタンス化は前後関係ない
ようです