ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2023年9月10日

JavaScriptやTypeScriptで検索しづらい演算子

単語ではないものはネットで検索しづらい。「!」や「?.」などだ。
以前記事にしたようなしてないような・・・忘れたので調べ直す。

 

?.

これは TypeScript ではなく JavaScript にある演算子である。
オプショナルチェーンと呼ぶそうだが、英語では optional chaining だ。

オプショナルチェーン演算子
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Optional_chaining

ドットまで含めての演算子になっている。ちなみに「.」は chaining operator と呼ぶそうだ。

リンク先では「adventurer」のプロパティに「dog」がないので「adventurer.dog?.name」となっている。
if文にするならこんな感じにして dog の有無を確認するだろう。

if (adventurer.dog) {
  console.log(adventurer.dog.name)
}

チェックをして dog の時点で undefined とわかっているためか「adventurer.dog?.name.hello」でも「adventurer.dog?.name.hello.world」でも、それから先の chaining operator でつないだ部分は気にしないようだ。

次の行の「adventurer.someNonExistentMethod?.()」はメソッドの有無をチェックしている。
こんな感じで定義をしてやれば呼び出される。

const adventurer = {
  ...
  someNonExistentMethod: () => 'hello',
};

まあ、メソッドの有無は大きい問題になりそうだから、通常は if でチェックするんじゃないかな。

 

 

!

これはおそらく TypeScript の演算子だろう。
Non-null assertion operator と呼ばれる。

Non-null assertion operator
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator

これは「なくてもいいですよ」変数の「?」と逆のような意味といってよいかもしれん。

たぶん「なくてもいいですよ」の「?」も TypeScript だと思うが、これは演算子ではなく型定義でしか使えないはずだ。
option parameter と呼ぶそうであるが TypeScript のドキュメントからは見つけられなかった。

 

とはいえ、何に使えるのか。null じゃないですよ、といいつつも実装上 null になることがありうるのをチェックするようになる? でもチェックして何ができるというのか。例外を出して終わらせるのであれば「!」がなくても同じだろう。

こちらによると、コンパイラに明示するのが目的らしい。

TypeScriptでよく見る「?」「!」と仲良くしたい
https://zenn.dev/oreo2990/articles/3d780560c5e552

たしかに、「?」にした変数だったりするとわざわざ if 文で「== null だったら処理を続けない」みたいなのを書かないとエラーになっていた気がするので、そういうのを書かなくてよいのかもしれない。
が、それだったらむしろ if 文にしておかないと危ないのでは? ちょっとテストコードを書くときに、そこで落ちてもいいからコンパイラは無視してくれってことだったらありなのかな。

 

その次に載っていた「明確な割り当てアサーション」は「?」の逆だ。
私はこのオプションを知らなかったが、これも「!」演算子と似たような感じだろう。
確かに書いていてエラーになるのだが、私は初期値を適当に突っ込んでるな。初期値を突っ込むことでロジックが間違っていることには気付きにくくなるかもしれないので、そういうのの洗い出しにはよいかもしれん。

2022年11月13日

[npm] systemctl で npm を動かすのは面倒そうだ

お仕事で JavaScript というか TypeScript というか、node.js で動くアプリを作った。
サーバみたいなもので、ずっと動いていてほしい。

となるt、systemd というか systemctl というか、そういうやつで動いていてほしい。


最初は node.js へのフルパスを書いて、直接パラメータなども書いて動かしていた。
が、パラメータの種類が増えて、最後の方になると TypeScript のトランスパイルも行いたくなったので npm で pre などを付けて開発をしていた。

そこまでくると、node.js ではなく npm の方を使って systemctl で実行して欲しいと思うのが人情だろう。

npm も同じようにフルパスで書いてあげると、それは systemctl で動いた。
しかしそこから node を実行するようにしていると、そちらは PATH が通っていないために実行エラーになってしまった。
systemctl が動くユーザでも動くような PATH に置いてあれば良かったのだが、node は nvm とか nvs のようなバージョン管理している方のパスに置いていたのだった。
まあ、動かんよね。

 

検索してみたものの、あまり対策はなさそうだった。

  • node はパスが通ったところに置く
    • そうするとバージョン管理している意味が無いよね
  • スクリプトファイルを置いてそちらに全部書く
    • まあこっちが妥当なのかな
  • node をフルパスで書いて npm を使わない

というわけで、今回は 最後のパターンを使った。
systemctl みたいな権限を使って実行するのだったら、パスの回避だけでスクリプトを作りたくなかったというところか。スクリプトで実行すること自体に意味があるんだったらやったのだけど、今回はそうじゃなかったからなぁ。

2022年7月9日

[javascript] プリミティブ型と toString()

困ったことに、JavaScript の try-catch で catch するものは Error 型とは限らないそうだ。
取りあえずログに出したかったのだが、JSON.stringify(e) で {} になったり、 e で [Object Object] になったりして、もう考えるのが面倒になってきた。
Error 型だったら、e.message と e.name はあるのだけど、ライブラリを使っていると派生したオブジェクトを返したりして、もうどうしてよいやら・・・。

Error - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Error

よく見ると、インスタンスメソッドで toString() があった。

Error - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#instance_methods

Error を派生したんだったら toString() もオーバーライドしてほどほどの文字列を作るのが礼儀なんじゃなかろうか、きっとそうだ、と思い込むことにした。

 

しかし、throw が Error 型じゃなくてもよいということはプリミティブ型でもよいはずだ。
プリミティブ型はオブジェクトじゃないはずだ。
オブジェクトじゃないなら Object を継承しないので toString() を持たないかもしれない。
だったら e の型チェックしてからじゃないと toString() が使えないのか。
めんどくさい・・・。

 

と思って文字列や数字を throw したのだが、toString() で文字列として出力されていた。
なんで??

本を読んだところ、プリミティブ型は使われる際にラッパーオブジェクトが一時的に生成されるようになっているらしい。
つまり、文字列なら String型、数字なら Number型が生成される。
これらはオブジェクトなので toString() を使える、という理屈のようだ。

もしかしたら toString() などのメソッドを使う直前に生成されるのかもしれん。そっちの方が理屈に合うか。

 

それはそうと、元の問題である catch したデータをログに出した意見だが、これは昔からどうしたもんだかって話のようだ。 Error の派生についてStackoverflow のリンクが載っているくらいだから、 throw となるとさらにめんどくさそうだ。

throw - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/throw

オンラインでコードを動かせるので throw を書き換えながら試したが、 new Error(文字列) は console.log で出力できるが、単に Object として {message: 文字列} なんかを throw すると [object Object] になる。
じゃあ console.log で JSON.stringify() したものを出せば良いのだが、 new Error() だと {} だけになる。

 

そういえば、console.log を使いたいわけでは無く、ログファイルに出力させたいので「文字列」として取得したいのを忘れていた。 console.dir が使えるという記事があったのだけど、そういうわけで今回は対象外だ。

となると、無難に toString() しておけば文字列として取得できるのか。
オブジェクトの中を全て見れば詳細な情報が確認できる場合もありそうだが、そこはもうピンポイントで調べたい場合だけとあきらめても許してくれるんじゃないだろうか。

2022年5月22日

[typescript] インデックスシグネチャ?

覚えたい言語がいくつかある。 TypeScript, Kotlin, Go, Rust。
Rust が気持ちとしては強いのだけど、お仕事で今のところ使っていないので優先度が下がっている。
TypeScript と Go が一番多いのだが、どうやってもできないときに調べるくらいで、あとは適当に書いたら適当に動く(他のところをまねするのも含む)ので、なんとかなっている(と思いたい)。

それでもたまには本を読んで勉強する。たまにはじゃダメだろうと思うが許してほしい。
また OJT だけだと出てこないやり方に気付かないこともあるので、そこそこ体系的な勉強もいると思うのだ。

 

今日、 TypeScript の本を読んで出てきたのが「インデックスシグネチャ」だ。


TypeScript の本に出てきたのだが、インデックスシグネチャ自体は JavaScript から存在する書き方らしい。
MDN で検索したけど出てこないので、よくわからん。

オブジェクトのメンバーを参照するとき、普段は xxObj.yyItem のようにドットで指定するが、これを xxObje['zz'] のように参照するやり方のようだ。ドットで指定するやり方だと実装時にわかっていないと書けないが、添字で指定できるなら動的に参照できるというのがメリットだと思う。

Index signature(インデックス型) - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/type-system/index-signatures

JavaScript の方を良くしらんのだが、TypeScript の場合はインデックスとして使えるのが string か number に限定されるとのこと。

 

なんとなくわかったつもりになったが、これってキーとなる型が number か string の連想配列と考えてもよいのではないだろうか。
連想配列だったら Map がある。

Map - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map

同じことができそうなのだが、違いは何だろうか? JavaScript 自体には連想配列はこれだ、みたいな定義が無いとかかもしれんが、 Object と Map がそういう立ち位置のようだ。

いろいろ覚えたくない人としては Map だけ覚えておけばいい、と誰かに言ってもらえると助かるのだがね。

2022年5月15日

[typescript] ちょっと触った感想

週末に typescript を少し触っていたので感想を残しておこう。

 

トランスパイル後のjsファイルもcommitしたくなる

ts ファイルがあってもトランスパイルしないと node.js で動かせない。
トランスパイル使用としたら tsc をインストールしないといかん。
大したことじゃないのだけど、全然大した作業じゃないのだけど、クラウドに VM 立てて、 node.js と npm が使えるようになって、 git clone してさあ npm start、ってやったら js がないってなったときにちょっとガックリするのだよね。

ts-node がよく紹介されるので使っていたけど、リポジトリ見たら結構大きい・・・。
それなら Makefile 作って、make install、make、npm start、の 3ステップくらいにしたらよいんだろうか。
でも make も何かインストールしないと使えないので、それはそれでって感じだ。

今回は gRPC のためにやっていたので proto ファイルもある。
proto ファイルからツールを使って d.ts ファイルと js ファイルを作ってもらい、それを使った ts ファイルを作り、トランスパイルして js ファイルを作ってもらう。

最後には proto も ts もいらなくなるのだから、なんとなく悲哀を感じてしまった。
node.js みたいに node.ts とかあるとよいのだけどね。

 

トランスパイルしたファイルのディレクトリ

tsconfig に "outDir" があったので "./out" にしてみた。
proto ファイルから d.ts ファイルと js ファイルを作るので、それは "./proto" に置いてみた。
そうすると tsc は成功するのだが node.js で実行しようとするとエラーになる。
import する場所が "./proto" なのだけど js ファイルがあるのは "./out" なので、実際には "../proto" にあることになるからだ(あとから js ファイルを ./out/proto に置けば良いことに気付いたがね)。

rootDirs に "./proto" を追加して import は相対パスで指定すればよいと思ったのだけどダメだった。
「This does not affect how TypeScript emits JavaScript」とあるからトランスパイル後のパスまでは考慮しないってことだと思うけど、tsc で効いてない理由が分からん。

私は挫折して d.ts ファイルをカレントディレクトリに持ってきて "./" で参照するようにし、js ファイルは "./out" に置くようにした。

TypeScript の paths はパスを解決してくれないので注意すべし! – 自主的20%るぅる
https://www.agent-grow.com/self20percent/2019/03/11/typescript-paths-work-careful/

これは paths なのでちょっと違うけど、import が解決できないという意味では同じだ。 ts-patch , typescript-transform-paths を使ってみたが、なんかもう一手間いる感じがした。

[typescript] 複数のimportするファイルを何とかしたい

タイトルだと全然わからんのでちゃんと説明をします......

 

発端は gRPC クライアントアプリを JavaScript で作っていたので TypeScript にしようと考えたことだった。
いままで TypeScript のことを調べはしたものの、 1ファイルで終わるような内容であれば JavaScript で済ませていたのだが、大きくなりそうな気配があったので TypeScript にしておこうとしたのだ。

--init で tsconfig.json を作り、ちょっとだけ変更して、拡張子を js から ts にリネームし、プリミティブ型以外のところは any で逃げてコンパイルというかトランスパイルというか、ともかく JavaScript にして node.js で実行するところまではできた。

次の段階は any を何かの型に置き換える作業だろうと思って進めていた。
作っていた JavaScript の中では特に Object を作るようなことはしていなかったのだが、 gRPC のリクエストとレスポンスは proto ファイルに由来する型を使っていた。

では proto ファイルから型を作る何かがあるだろうと調べて出てきたのがこれだった。

Generating TypeScript types
https://github.com/grpc/grpc-node/tree/master/packages/proto-loader#generating-typescript-types

npx proto-loader-gen-types を実行すると、proto ファイルを読み込んで型情報を持つ ts ファイルを作ってくれた。
そこまでは良かったのだが、proto ファイルに記述されている message ごとに ts ファイルを作るようだった。使う予定の proto ファイルにはたくさん message があり、大量の ts ファイルが生成された。

これを import すれば使えそうなのだが、その前に「この大量のファイルを import するということ自体を何とかできんだろうか、と考えたのだ。


まず考えたのがワイルドカード。
import もモジュールといいつつファイル名みたいなもんだから from のところに './proto/*' みたいな書き方をすればよいんじゃないの?と思うのは仕方あるまい。

結果としてダメだった。
まあ、同じディレクトリにあるだけで読み込まれるってのはさすがに危険すぎるな。

 

dynamic import というやり方も見つけたのだが、別に静的で良いのだ。
単に import 文をたくさん書きたくないだけなのだよ。
いや、書いても良いけど import だけで何十行もあるような状況が嫌なのだよ。

 

というわけで次に思いついたのが、別の ts ファイルに import だけ詰め込んで それを import するような手段が執れないだろうか、ということだった。

import - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

JavaScript だろうと TypeScript だろうと import は import のはずだから MDN の説明でよいと思う。
ただね、読む前に想像すると、そんなことはできないんじゃないかと思うのだ。
まあ、やらんとわからん。

 

image

image

>node index.mjs
aaa=30
bbb=20
ccc=10

TypeScript ではないが、まあよかろう。
これが基本形だ。

 

ダメだったワイルドカード。

import * as aaa from './files/*.js';

console.log(`aaa=${aaa.A}`);
console.log(`bbb=${aaa.B}`);
console.log(`ccc=${aaa.C}`);

Cannot find module になる。
ワイルドカードと見ずに "*.js" というモジュール名として処理するからだろう。

 

a.js, b.js, c.js は変更しないとして files/index.mjs を追加してみた。
まあ、ダメなんだけどね。

image

>node index.mjs
aaa=undefined
bbb=undefined
ccc=undefined

import でモジュール名しか指定していない場合は import はしないのだね。。。

副作用のためだけにモジュールをインポートする
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only

それに、もし import できていたとしても export していないから参照できないだろう。
これなら動いた。

image

>node index.mjs
30
20
10

export {a.A} みたいな書き方をすれば total.A で参照できるかと思ったがダメだった。 export {a.A as A} もダメだ。
再export すればいける。

image

export from という書き方もあるそうだ。

export
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export

image

これくらいだったら bash で機械的に作れるんじゃないかな。

2022年3月27日

[RN] 自作証明書をクライアントで使いたい

Android で、aarで組み込んだライブラリの方で HTTPS サーバが起動していて、アプリはその HTTPS サーバに接続してリクエストするような作りになっているとしよう。
その HTTPS サーバは公開しないけど、なんかあったら嫌なので自分で作った証明書を使っている。
さて、そこにどうやってアクセスすると良いのだろうか?

 

最初は「JavaScript self signed」などで検索していたのだが、よく出てくるのが「reject unauthorizedオプションを無効にする」というやつだった。
どう見ても、認証を無視する設定だと思う。開発中はまだよいかもしれんが、今回はしょっちゅう証明書を作り直すわけでもないから認証する方で実装しておきたい。

ようやく出てきたのがこちら。

javascript - nodejs - error self signed certificate in certificate chain - Stack Overflow
https://stackoverflow.com/questions/45088006/nodejs-error-self-signed-certificate-in-certificate-chain

'ca' などというオプションがあるのね。
しかしこれ、 require で 'https' とかやってるし、React Native では使えない気がする。


そう、今回は React Native でやりたいのだ。
React Native は Node.js ではないので同じことができるとは限らない。

Networking · React Native
https://reactnative.dev/docs/network

fetch() は聞いたことがある。
Mozzila の MDN 参照と言うことは node-fetch ではなく標準な fetch() なのだろう。
ということはファイルシステムなんかは使えないと思う。

[Android]How to fetch() from https server with self-signed certificate · Issue #6995 · facebook/react-native
https://github.com/facebook/react-native/issues/6995

うーーーん・・・・・・・・・。

これは Native 側に実装して呼び出すようにした方が簡単だし確実そうだなぁ。

2022年3月12日

[js] Uint8Array はオブジェクト

まとめ

  • React Native → Android へ Uint8Array を渡したいとしても、base64 でエンコードするのが無難そう

 


おはなし

前の記事で ArrayList<Byte> を ByteArray に変換する話をした。
しかし、その前の条件が間違えていた。
React Native から Uint8Array を Android に渡したのだが、Uint8Array はオブジェクトであって配列ではないということだ。

Error: Exception in HostFunction: Malformed calls from JS: field sizes are different.

こんな例外が発生したのだ。

たぶん number[] を渡せばうまくいくのだろうが、やりたいことはそうではない。
おそらく Uint8Array も Kotlin の ByteArray と同じように効率が良いオブジェクトなのだと思う。
それを for とかでぐるぐる回して number[] に入れて渡すなんてことはしたくないのだ。

 

そんなわけで Uint8Array を眺めている。

Uint8Array
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

まず、 Uint8Array.toString() で文字列にして String.toByteArray() で戻してみた。
・・・ダメ。
toString() は中のデータを無理やり文字列として扱う、ではなく、中のデータを 1バイトずつコンマ区切りで出力する、というものだった。

toLocaleString() がたぶんそういうことをしてくれそうなのだけど、locale の指定をするということは何かしらの変換をバイト単位とかで行うということだと思う。
それだったら・・・それだったら base64 にしてやりとりするのが一番無難じゃないか!

base64 なら無理やりな変換ではなくバイナリ向きだし、文字列になるし、何も問題はない。
問題なのは、せっかくプリミティブ向けに型を作ってあるのに申し訳ないという気持ちだけだ。

 

というわけで、こうなりましたとさ。

JavaScript側 : base64String = Buffer.from(Uint8ArrayData).toString('base64')
Android側 : Base64.decode(base64String, Base64.DEFAULT)

2022年2月27日

Windows10とnvm

JavaScript を使うことがある。主に node.js で。

node.js を Windows で使う場合、node.js のインストーラをダウンロードしてインストールするだろう。
しかし、環境によって node.js のバージョンを指定されることがある。
Java で jdk8 だの jdk11 だのの選択があるのと同じようなものだ。
個人で開発するときならまだよいのだが、プロジェクトでやるときには同じバージョンをインストールしていないと面倒なことになりかねない。

 

Linux で作業するときには nvm を使っていた。

nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions
https://github.com/nvm-sh/nvm

POSIXコンパチブルと書いてあるように、Windows だと使えない。
似たようなものとして nvm-windows がある。

coreybutler/nvm-windows: A node.js version management utility for Windows. Ironically written in Go.
https://github.com/coreybutler/nvm-windows

GitHub からバイナリをダウンロードしてインストールすればよいのだが、Chocolatey で環境を作るようにしているのでそちらでインストールを今までしていた。
ただ、これを書いている時点(2022/02/27)で GitHub でのバージョンは 1.1.9 なのだが、Chocolatey の方は 1.1.5 か 1.1.7 だった。
なので、比較的最近まで 1.1.7 をインストールして使っていた。

 

最近まで 1つの node.js バージョン(v14系)しか必要としていなかったのだが、v16系を使う必要が出てきた。
ようやく nvm が nvm っぽいことをするときがやってきたのだ。
v16.14.0 をインストールすることはできたのだが、 npm i するとエラーになるのだ。

エラーメッセージがなんだったかもう忘れてしまったのだが、1.1.9 をインストールするとよいという回答が見つかったので GitHub 版にしたらエラーが出なくなった。

その後 1.1.7 に入れ替えても動いたのだが、ようわからんので 1.1.9 のままにしている。
おそらく Chocolatey の nvmは chocolatey-nvm-windows が使われているのだと思う。

なら chocolatey の nvm でバージョンを 1.1.9 と指定したらいけるんじゃないの?と試してみたのだが、nvm 1.1.5 の方でバージョンを指定しても 1.1.7 がインストールされてしまった。ダメか。


node.js を普通にインストーラーでインストールすると、 Program Files\node.js というディレクトリを作ってインストールされる。
nvm 1.1.7 を使うと Program Files\node.js はショートカットというかたぶんシンボリックリンクになって、本体が ProgramData\node.js に置かれるようになっていたと思う。

 

nvm 1.1.9 では AppData\Roaming\nvm にインストールされた。
しかし「nvm use」をそのまま実行するとエラーになる。

>nvm use 16.14.0
exit status 1: �A�N�Z�X�����ۂ����܂����B

読めないのだ・・・。

管理者権限を持った状態で nvm use すると動作する。
そのときに Program Files\node.js にシンボリックリンクが張られた。

やっぱりオリジナルが Program Files\node.js にインストールするようになっているので避けられないのだろうか。
これはこれで面倒だ。


Microsoft のページに代替のマネージャーが載っていた。

代替のバージョン マネージャー
https://docs.microsoft.com/ja-jp/windows/dev-environment/javascript/nodejs-on-windows#alternative-version-managers

vscode とも連携できるようなので nvs をインストールしてみた。
Chocolatey からもできる。それに Windows 以外でも使える。

jasongin/nvs: Node Version Switcher - A cross-platform tool for switching between versions and forks of Node.js
https://github.com/jasongin/nvs

コマンドは nvm と同じような感じだが、 nvs add を使うのがよいのかな。

>nvs add 16.14.0
Downloading [#############################################################] 100%
Extracting  [#############################################################] 100%

>nvs use 16.14.0
PATH += %LOCALAPPDATA%\nvs\node\16.14.0\x64

>node --version
v16.14.0

ほう。
nvs use はその場で PATH に追加するだけなので権限は必要にならなかった。

 

それになんと、ディレクトリごとに自動で切り替える機能もある。
bash と powershell 向けと書いてあるが・・・あれ、cmd.exe では使えないのだろうか。

>nvs auto on
Automatic switching is not supported from a Windows Command Prompt.
Use PowerShell instead.

ダメか。
なんか powershell は苦手なのだが、node.js で使うだけだから我慢しよう。
文字色がダメなんだと思う。「node --version」だとこうなるのだ。

image

背景が真っ黒なら、まだなんとかなるか。

image

色をカスタマイズしたいと言うよりは、色分けを止めてほしいだけなのだが、見つからなかった。
それに、vscode を起動して自動的に Terminal が希望したディレクトリを開いた場合には auto が効かないようだったので無理して powershell を使う必要もなさそうだ。

 

手動で行う場合は、.node-version というファイルを作ってバージョン名を書いておく。
そして nvs use と打つと設定してくれる。
.nvmrc でもよいように書いてあったのだが、そちらだと動かなかった。 cmd.exe だったからか?
まあいいや。

Linux でやるときには nvm と direnv を組み合わせていたのだけど、 nvs ならこれだけで切り替えられるので便利そうだ。

 

あとはまあ、無意識のうちに nvm と打ち込んでしまうのが難点というかまだ慣れていないというかだな。

2021年9月4日

[js]JSONっぽいデータをJavaScriptで読みたい

JSONのデータはこんな感じだ。

{
  "name": "Yoshio",
  "age": 92
}

 

JSONっぽいデータと書いたが、今回はこういうデータのことである。

{
  name: "Yoshio",
  age: 92
}

「key: value」と呼ぶとしたら、keyの部分にダブルクォーテーションがないタイプのデータである。
JavaScriptのコード中に書くようなデータと言えばよいか。

 

何でか知らんが、サーバがこういうデータを返してくるようなのだ。
それをJavaScriptで読んで処理をしたい。


一応取り込めたのだが、この方法はあまりよろしくないとさんざん書かれている。

const VALUE = "{name: 'Yoshio', age: 92}";
const result = eval(`(${VALUE})`);
console.log(JSON.stringify(result));

$ node ev.js
{"name":"Yoshio","age":92}

 

よろしくない理由は eval() を使うと危険なためである。

置き換えの例としてFunctionを使うよう書かれていた。

const VALUE = "{name: 'Yoshio', age: 92}";
const result = Function(`return ${VALUE}`)();
console.log(JSON.stringify(result));

$ node ev.js
{"name":"Yoshio","age":92}

 

このシンプルなデータだとFunctionで置き換えられたのだが、複雑になるとそうもいかないようなのだが、どうしたらよいかわからん。。。

2021年7月4日

[js]import * は普通

前回、namespace にして export default して import させていた。
namespace は無しにして import * でも同じことはできるのだが、わざわざ namespace にしてみたのだ。

その理由は「ワイルドカードよりも特定の namespace を使った方がよいのではないか」という気持ちがあったからだ。ほら、不特定なものよりも特定した方が好まれるじゃないか。

しかし。。。

TypeScript: Documentation - Namespaces and Modules
https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html

太字で

we recommended modules over namespaces in modern code.

最新のコードでは、名前空間よりもモジュールを推奨しました。
(Google翻訳)

と書いてある。
「最新のコードでは」だから将来変わるのかもしれんが、2021/07/04 19:23 ではそうなっていた。

 

なので、ワイルドカードで指定しているとかそういうのはあんまあり考えなくて良いんだろうね。変に気を回して時間を掛けてしまったよ。

[js]ファイルを分けたい

大きいプログラムを作ると、ファイルを分けたくなるだろう。そうじゃなくても、機能ごとにファイルを分けたいだろうし。
まあ、私が分けたいのだ。


index.ts

01: import * as greet from './greet';
02: 
03: const hello: greet.Greet = greet.hello();
04: if (hello != null) {
05:     console.log(`hello: ${JSON.stringify(hello)}`);
06: }

greet/index.ts

01: export type Greet = {
02:     greet: string;
03:     time?: string;
04: };
05: 
06: export function hello(): Greet {
07:     return {greet: 'hello', time: 'allday'};
08: }
09: 
10: export default {}
  • greet ディレクトリにファイルがあるので、 import の from はそこを指定する。
    • ファイル名が index.ts なのでディレクトリの指定だけで済んでいる。
  • greet/index.ts は export default {} としたので、ファイルの中にある export 指定したものは同じ名前で export される。

という理解で正しいだろうか?

 

import の from より前の部分があまりわかってない。
こうか?

  • 個別に import するなら、 import {個別にコンマ区切り} from '~~'
  • 全部 import するなら、 import 名前 from '~~'

 

greet/index.ts から個別に importすると、こうなる。

01: import {Greet,hello} from './greet';
02: 
03: const msg: Greet = hello();
04: if (msg!= null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

列挙するのが面倒ではあるが、使用しているものがはっきりするので場合によってはわかりやすい。


じゃあ全部 import するので import Hello from './greet' でよいかというと、これはダメそうだ。
Hello.Greet や Hello.hello という指定ができなかったのだ。
「Cannot find namespace 'Hello'」というエラーになる。

namespace を使う場合はこういう書き方になるようだ。
import するときに「import Hello from './greet'」としたが、別に namespace名と同じにする必要はなさそうだった。

あと、namespace も export を指定しないと個別に import できない(個別の話。export default指定しているなら関係ない)。namespace を export しても、namespace の中身が全部 export されたことになるわけではなく、その中でも export を付けたものだけが外部から見えるようだ。

index.ts

01: import Hello from './greet';
02: 
03: const msg: Hello.Greet = Hello.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

greet/index.ts

01: namespace Hello {
02:     export type Greet = {
03:         greet: string;
04:         time?: string;
05:     };
06: 
07:     export function hello(): Greet {
08:         return {greet: 'hello', time: 'allday'};
09:     }
10: }
11: export default Hello;

 

この greet/index.ts を import * した場合は namespace 名を使わなくても済むようだ。

index.ts

01: import * as Pochi from './greet';
02: 
03: const msg: Pochi.default.Greet = Pochi.default.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

 

 

namespace を付けるなら、 export default ではなく個別にした方がよいのかな?と思ったが、そうすると import側でも個別にしないといけなかった。
1ファイルで複数の namespace を入れ込むことは(私の実装だと)やらないので、これは default のままの方が使い勝手がよいか。

index.ts

01: import {Hello} from './greet';
02: 
03: const msg: Hello.Greet = Hello.hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

greet/index.ts

01: namespace Hello {
02:     export type Greet = {
03:         greet: string;
04:         time?: string;
05:     };
06: 
07:     export function hello(): Greet {
08:         return {greet: 'hello', time: 'allday'};
09:     }
10: }
11: export {Hello};

 


import の後ろは、 * as xx だったり name だったり {name} だったりといろいろあるが、いったいなんなんだ。

ECMAScript 2015 Language Specification – ECMA-262 6th Edition
https://262.ecma-international.org/6.0/#sec-imports

たくさんあるので from を使うパターンだけ見ていこう。
以下、イタリック&下線の部分はさらに展開されるという意味である。

import ImportClause FromClause;

FromClause の方は種類がないので、先にそちらを。

from StringLiteral

順番に展開すると ModuleSpecifier → StringLiteral なのだが、まあよかろう。
つまり文字列だ。

ImportClause は5種類に分かれる。

  • ImportedDefaultBinding
  • * as BindingIdentifier
  • { ImportsList }
  • ImportedDefaultBinding , * as BindingIdentifier
  • ImportedDefaultBinding , { ImportsList }

たぶん BindingIdentifier は「勝手に割り当てて良い名称」だと思う。bind だから紐付ける名称になるのかな。

上3つが import でよく見る形式で、残り2つはその組み合わせだ。
使えるものを展開してるのはこちら。

import - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import

まあ、こっちの方がわかりやすいな。日本語だし。


これで基本的なところはできたんじゃなかろうか。

もう1つやりたいのが、export するファイルとそうじゃないファイルを分ける作業だ。
実装を見られて困るとかではなく、C言語で言うところのヘッダファイルとソースファイルに分けたいというだけである。

できるのだろうか?
なお、tsconfig.js は isolatedModules:true を指定しているものとする。

 

re-export というのでやってみた。

 

greet/index.d.ts

01: export type Greet = {
02:     greet: string;
03:     time?: string;
04: };

greet/hello.ts

01: import {Greet} from './index.d';
02: 
03: export function hello(): Greet {
04:     return {greet: 'hello', time: 'allday'};
05: }

 

greet/index.ts

01: export {hello} from './hello';
02: export type {Greet} from './index.d';

 

index.ts

01: import {hello,Greet} from './greet';
02: 
03: const msg: Greet = hello();
04: if (msg != null) {
05:     console.log(`hello: ${JSON.stringify(msg)}`);
06: }

うーん、無理やりやりましたという感じが拭えない。
それに呼び出し元の index.ts の方に修正が入るのはダメだ。

 

これは書き方としてダメだった。namespace 内ではダメだそうだ。

01: export namespace Hello {
02:     export {hello} from './hello';
03:     export type {Greet} from './index.d';
04: };
05: export default Hello;

 

これならいけるようだ。

01: import {hello as myHello} from './hello';
02: import {Greet as myGreet} from './index.d';
03: 
04: export namespace Hello {
05:     export const hello = myHello;
06:     export type Greet = myGreet;
07: };
08: export default Hello;

うーん・・・。もっとよいやり方がありそうな気がする。

 

というわけで、あきらめて本を買うことにした。

O'Reilly Japan - プログラミングTypeScript
https://www.oreilly.co.jp/books/9784873119045/

本の中身は書かないのだが、私が import あたりをうまく理解できない理由が分かってきた気がする。

「JavaScript」とか「TypeScript」とかで検索しながらやっていたけど、import というかモジュールの扱いについて過去の経緯がいろいろあり、ネットで見つけた情報はどの時点の JavaScript について書いているかによって変わってくるからだと思う。そういうのを知らずにネットで調べていたので、あっちの情報とこっちの情報をまぜこぜにしてうまくいかなかったとか、そんな感じになったんじゃなかろうか。

本の内容で勉強することの利点は、よくも悪くも情報が混ざらないことだと思うので、あやふやな知識でふらふら調べるよりはましなんじゃなかろうか。せっかくお金出したんだから、ましであってほしい。

2021年7月3日

[js]nullチェックとtruthy

JavaScript で null チェックする際、私はこう書くことが多い。

let val;

...

if (val) {
  // not null
  ...
} else {
  // null
  ...
}

C言語だと 0 以外の値なら真なのでアドレス値が入っているかどうかを判定するし、 Java だとそういう書き方は興られたような気がする。

で、JavaScript の場合はどういう仕様なのか?

 

Truthy - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
https://developer.mozilla.org/ja/docs/Glossary/Truthy

falsy として定義された値以外は truthy 扱いで、 truthy は boolean 判定するところに出てきたら true として扱うという仕様だった。

falsy はこれら。

  • false
  • 0
  • -0
  • 0n
  • ''(空文字列)
  • null
  • undefined
  • NaN

オブジェクトの場合は null か undefined かそれ以外になるはずなので、 if (オブジェクトの変数) だけで事足りるはずだ。


あとは、その書き方が一般的なのかどうか、だ。
私は if (変数) や if (!変数) の書き方で見慣れているのだが、一般的にそうでないなら使いどころは絞った方がよいと思うのだ。

nullとundefined - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/recap/null-undefined

うーん「== null」って書いてあるな。

確かに、 != null だと null か undefined だけだが、変数名だけだと幅が広すぎるのか。
x && x.length > 0 とか x && x !== 0 だったら変数名だけでよいかもしれないが、やっぱりちゃんと書いた方が無難だろう。

 

というわけで、 null / undefined のチェックにはきちんと == null のように条件を書くことにしよう。


ついでに読んでいてわからなかったのがこちら。

ルートレベル(root level)のundefinedのチェック
https://typescript-jp.gitbook.io/deep-dive/recap/null-undefined#rtoreberuroot-levelnoundefinednochekku

null だろうと undefined だろうと、 x.y を参照するのに x が どこも指していないのだったらダメだろう。

undefined かどうかをチェックするには確かに typeof するしかないのだが、 null は別によいってことなんだろうか。

うーん、わからん。。。

 

ごにょごにょ考えたが、あれは x.y != null みたいなチェックをいきなりするのではなく、先に x をチェックしろということを言いたいだけなんだろうか。
それならわかるけど、やっぱり x != null だけでよいと思うのだ。

Checking for root level undefined
https://basarat.gitbook.io/typescript/recap/null-undefined#checking-for-root-level-undefined

原文も同じ。

まあいいや。
ルートレベルより下のプロパティやメソッドにアクセスする前にはルートレベルのチェックをするか try-catch を考慮するようにしておけば良かろう。

TypeScriptとnull/undefinedチェック

JavaScript のことを書いているサイトであればほぼ書かれていると思われる null/undefined チェック問題。問題というよりは FAQ なんだろうけど、私は何度やっても覚えられない。


まず、 null と undefined の型だが、これはオブジェクトではなくプリミティブ型とのこと。昔ちょっと JavaScript を書いたときは 'undefined' と文字列になっていたような気がするが、そう書いてある()からそうなんだろう。

にもかかわらず、 typeof null は "object" だ。 typeof undefined は "undefined" である。
nullは「プリミティブ値」と表現されていたし、undefined も同様だ。

Primitive (プリミティブ) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
https://developer.mozilla.org/ja/docs/Glossary/Primitive

undefinedはプリミティブデータ型なので typeof で "undefined" になるが、 null は「プリミティブに見える」と微妙な表現をされている特殊なオブジェクトだそうな。あらゆるオブジェクトは nullから派生するらしい。

 

等値演算子(==, !=)を使う場合、null と undefined は同類と見なされる。

01: let val = null;
02: 
03: if (val) {
04:   console.log('defined');
05: } else {
06:   console.log('undefined');
07: }

これはどっちを通ったかというと、"undefined" の方を通った。 1行目が undefined でも同じだ。

null と undefined を区別したいなら同値演算子(===, !==)を使う。

A user name of null?
https://developer.mozilla.org/ja/docs/Learn/Getting_started_with_the_web/JavaScript_basics#a_user_name_of_null

この例は (!myName || myName === null) となっているのだが、 !myName だけでよいのでは?
もし && だったら myName === null だけでよいだろうし。

 

  • undefined はプリミティブデータ型でありプリミティブ値である
  • null はプリミティブ値のようでありオブジェクトのようであり、特殊な扱い
  • undefined と null を同じ扱いにして良いなら等値演算子(==, !=)で扱える
    • x == null とか x == undefined とか
    • !x でもよい
  • undefined かどうかを調べたいなら typeof === 'undefined' にする
    • null の typeof は 'object' になる

 

ちなみに、変数に undefined を与えて console.log したのが上、 'undefined' を与えて console.log したのが下。

image

null だとどっちで与えても白い文字で出力された。 うーん。

console.log なのか JavaScript 全体なのか分からないけど、人間寄りになるように動いてしまうのが苦手意識を持ってしまう原因かもしれん(私がね)。


TypeScript は型定義があるため、もうちょっと複雑になりそうだ。

nullとundefined - TypeScript Deep Dive 日本語版
https://typescript-jp.gitbook.io/deep-dive/recap/null-undefined

等値演算子で null チェックすればよいよ、ということだ。

といっても、ここは TypeScript の仕様説明ではないので、もうちょっと確認しよう。

 

まず、 null が型の方として扱われるようだ。

let val: number = null;

これはコンパイルエラーだ。どうしても代入したければ、私は null も OKですよ、ということにするしかないのか。

let val: number | null = null;

ちなみに、これもダメだ。

let val: number = null as number;

 

null は型扱いなので、変数を null型で指定できるようだ。

let val: null = null;
val = 'hello';

こんなのは代入する行でコンパイルエラーになる。 undefined もそうだった。

let val: undefined;
val = null;

使い道はないけどね。

 

だから、 null や undefined を代入したいのなら型のところで | null のような形にすることになる。
なるのだが、 undefined はデフォルト値になるので、ちょっと特殊な扱いかもしれない。その代わり、代入しないまま使おうとしたら、 | undefined になっていない限りはエラーになる。型と異なる変数が入っている、という扱いかもしれないが、TS2454 コンパイルエラーは before being assigned なので別エラーにはなっている。


C言語をよく使う人からすると、使い終わった変数に null を代入したいという気持ちに駆られることがある。

開いたファイルを扱うオブジェクトの変数があって、その変数が null だったら先にオープンしてから処理を続ける、みたいな。それをやろうとすると、最初は undefined だからよいとして、一度オープンして、クローズしたら null にしたくなる。しかし null を代入させるなら | null で許容させる必要がある。

私の期分の問題かもしれないが、 | null というか、型を複数割り当てるというのがどうも苦手だ。 string | number とかされたら「どっちが入ってんだよ!」ってなるからやらないけど、似たような気持ちになってしまう。

なら指定しなければ良いのだが、そうじゃなかったら boolean の変数を用意して判断するみたいなことになるだろう。でも、そこまでするのはなんか大げさな気がしてしまう。

なので、あまり選びたくない2択のどちらかをせねばならぬ。
あるいは、そういう設計の仕方自体が JavaScript らしくないということかもしれない。

 

そういう、言語仕様ではなく、センスというかお作法というかがよくわかっていないというのが最大の悩みであった。

[js]Arrayと配列と型付き配列 (2)

こんな話を2回に分ける必要もないのだが、日記だから良いのだ。

Arrayクラスと型付き配列は別物っぽいという話だ。

JavaScript 型付き配列 - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Typed_arrays

型付き配列というのは、いわゆる C言語などでの配列に近いもののようである。ただ、C言語では単に型のデータを配列にするのだが、型付き配列は「xxx型の配列のクラス」であり、その型ごとにクラスが用意されているところが異なる。

なので、定義されたクラス以外は(デフォルトでは)存在しない。

  • Int8Array
  • Uint8Array
  • Uint8ClampedArray
  • Int16Array
  • Uint16Array
  • Int32Array
  • Uint32Array
  • Float32Array
  • Float64Array
  • BigInt64Array
  • BitUint64Array

まあ、こんだけあれば十分だね。

どうやら、JavaScript では最初に Arrayクラスがあって、その後で型付き配列ができたようだ。データがメモリとして連続しているという前提がないと高速化できないというのはよくわかる。

つまり、Arrayクラスとは別物であるし、成り立ちからしても Arrayクラスの派生ではないだろう。 ArrayBufferクラスというものがあるようなので、むしろそちらが基底クラスになっているだろうか? しかし分類としては Array側になっていた。まあ配列だしね。

索引付きコレクション
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects#indexed_collections

なんかこう、Java のドキュメントのように継承関係の図はないんだろうか。

2021年7月2日

[js]Arrayと配列と型付き配列 (1)

JavaScript というか TypeScript というか Node.js というか、とにかくその辺で実装をしている。

慣れない・・・。
同じことをするのに書き方がいくつかあるというのが混乱する。

特にアローね。

function x(val: number): number {
  return val + 2;
}

const x = (val) => val + 2;

アロー演算子でしか書けない場合があるのは良いとして、どっちでもよい場合が困るのだ。
function とか return とか、そこまで省略したいの??

ということを訴えてみたのだが、そういう言語だからしょうがないよな。
人間、あきらめも肝心だ。


今日は JavaScript で他のコードを読んでいて、知らない関数が出てきたので調べてみると、Arrayクラスの関数だったということがあった。
クラスだからメソッドだろうけど、そういうことではなく、import しないと使えないやつではない標準で提供されているものくらいは把握しておかないとつらいかろうということに気付いたのだ。

 

Array - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array

最初に書かれている解説で気になった言葉を箇条書きにする。

  • Arrayクラス
  • グローバルオブジェクト
  • 配列の構築に使用される
  • 型付き配列

まず、Arrayクラスはいいだろう。
「Arrayクラスはグローバルオブジェクト」と書いてあるので、importしなくても使えるのだろう。これもよい。
配列の構築に使用される、ということは、static関数みたいなのを持っていて、それで作ってあげたりするのだろう。

で、型付き配列とは挙動が異なるように読み取ることができる。

JavaScript 型付き配列 - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Typed_arrays

混同してはいけません、という文章があるので、ここに答があるに違いない!

が、今日は眠たいのでここまで。

2021年6月20日

[js]PouchDB

前回の RxDB は、PouchDB のアダプタが使えますよ、といっていた。
今回はその Pouch DB だ。

PouchDB, the JavaScript Database that Syncs!
https://pouchdb.com/

簡単に使えるならなんでもいいんだけどね。


find() は別になっているのでインストールがいるのだった。

npm i pouchdb pouchdb-find

01: const PouchDB = require('pouchdb');
02: PouchDB.plugin(require('pouchdb-find'));
03: 
04: const db = new PouchDB('kittens');
05: 
06: const fn = async () => {
07:     db.bulkDocs([
08:         {
09:             name: "yoshida",
10:             color: "green",
11:             healthpoints: 30,
12:         },
13:         {
14:             name: "yoshio",
15:             color: "blown",
16:             healthpoints: 33,
17:         },
18:         {
19:             name: "momo",
20:             color: "pink",
21:             healthpoints: 9,
22:         },
23:     ]);
24: 
25:     await db.createIndex({
26:         index: {fields: ['healthpoints']}
27:     });
28:     const resHealth = await db.find({
29:         selector: {healthpoints: {$exists: true}},
30:         sort: ['healthpoints'],
31:     });
32:     for (let lp = 0; lp < resHealth.docs.length; lp++) {
33:         console.log(resHealth.docs[lp].name);
34:     }
35:     console.log();
36: 
37:     await db.createIndex({
38:         index: {fields: ['color']}
39:     });
40:     const resColor = await db.find({
41:         selector: {color: {$exists: true}},
42:         sort: ['color'],
43:     });
44:     for (let lp = 0; lp < resColor.docs.length; lp++) {
45:         console.log(resColor.docs[lp].name);
46:     }
47: 
48:     await db.destroy();
49: };
50: fn();

createIndex()が、fields を列挙したらその分作られるのかと思ったのだが、それぞれ作ることになるようだ。戻り値で"created" か "exists" が取ってこれるが、存在するなら作らないと書いてあるので、そんなに気にしなくてよいのか。

また、createIndex() した後に put() したら再度 createIndex() がいるのかと思ったが、そうでもないようだ。今のコレクションに対してインデックスを作るという意味ではないのか。

 

TypeScriptの場合はこんな書き方で良いのかな?
find() したドキュメントがうまいことプロパティにアクセスできなかったので as を使ってキャストしてしまった。よいのかどうかわからん。

01: import * as PouchDB from 'pouchdb';
02: 
03: PouchDB.plugin(require('pouchdb-find'));
04: 
05: const db = new PouchDB.default('kittens');
06: 
07: type dbType = {
08:     _id: string;
09:     _rev: string;
10:     name: string;
11:     color: string;
12:     healthpoints: number;
13: };
14: const fn = async () => {
15:     db.bulkDocs([
16:         {
17:             name: "yoshida",
18:             color: "green",
19:             healthpoints: 30,
20:         },
21:         {
22:             name: "yoshio",
23:             color: "blown",
24:             healthpoints: 33,
25:         },
26:         {
27:             name: "momo",
28:             color: "pink",
29:             healthpoints: 9,
30:         },
31:     ]);
32: 
33:     let idxRes = await db.createIndex({
34:         index: { fields: ['healthpoints'] }
35:     });
36:     const resHealth = await db.find({
37:         selector: { healthpoints: { $exists: true } },
38:         sort: ['healthpoints'],
39:     });
40:     for (let lp = 0; lp < resHealth.docs.length; lp++) {
41:         const r = resHealth.docs[lp] as dbType;
42:         console.log(r.name);
43:     }
44:     console.log();
45: 
46:     idxRes = await db.createIndex({
47:         index: { fields: ['color'] }
48:     });
49:     const resColor = await db.find({
50:         selector: { color: { $exists: true } },
51:         sort: ['color'],
52:     });
53:     for (let lp = 0; lp < resColor.docs.length; lp++) {
54:         const r = resColor.docs[lp] as dbType;
55:         console.log(r.name);
56:     }
57: 
58:     await db.destroy();
59: };
60: fn();

[js]RxDB

JavaScript でデータベースを使う必要性が出てきてしまった。
練習してみよう。


Introduction · RxDB - Documentation
https://rxdb.info/

NoSQLデータベースだそうな。SQLite か LMDB かでいえば、LMDB 側ということになろう。
私はそんなにデータベースを使ったことがないので、比較できるのはその2つくらいなのだ。

npm i でインストールすれば使えるのだろうと思ったが、RxDB 以外にもインストールしないと使えないらしい。
adapter というものもインストールして設定しないと使えないっぽい。
今回は Node.js なので、adapter のページにあった leveldown というものを使ってみよう。 Level DBらしい。

npm i rxdb rxjs leveldown pouchdb-adapter-leveldb

 

メニューの順番からすると、 RxDatabase, RxSchema, RxCollection, RxDocument, RxQuery の順にやっていくのだろう。

SQL ではないからか、テーブルではなくスキーマと呼ぶのか? そこはまだよいのだが、コレクションとかドキュメントとかあるのだが、何なのかが分からん。。。

開発者が知っておくべき、ドキュメント・データベースの基礎:特集:MongoDBで理解する「ドキュメント・データベース」の世界(前編)(1/3 ページ) - @IT
https://www.atmarkit.co.jp/ait/articles/1211/09/news056.html

ドキュメントはデータベースに突っ込むデータで、コレクションはその集合・・・?

  • createRxDatabase()でデータベースを作る
  • データベースに突っ込みたいデータの構造を決めて addCollections() で追加する
  • insert() でデータを突っ込む
  • find() でデータを取り出す
01: import {
02:   createRxDatabase,
03:   addRxPlugin,
04: } from 'rxdb';
05: 
06: addRxPlugin(require('pouchdb-adapter-leveldb'));
07: const leveldown = require('leveldown');
08: 
09: const fn = async () => {
10:   const db = await createRxDatabase({
11:     name: 'heroesdb',
12:     adapter: leveldown,
13:   });
14: 
15:   const myHeroSchema = {
16:     "title": "hero schema",
17:     "version": 0,
18:     "description": "describes a simple hero",
19:     "type": "object",
20:     "properties": {
21:       "name": {
22:         "type": "string",
23:         "primary": true
24:       },
25:       "color": {
26:         "type": "string"
27:       },
28:       "healthpoints": {
29:         "type": "number",
30:         "minimum": 0,
31:         "maximum": 100
32:       },
33:     },
34:     "required": ["color"],
35:     "attachments": {
36:       "encrypted": false
37:     },
38:     indexes: ['healthpoints', 'color']
39:   }
40:   await db.addCollections({
41:     heroes: {
42:       schema: myHeroSchema
43:     }
44:   });
45: 
46:   await db.heroes.bulkInsert([
47:     {
48:       name: "yoshida",
49:       color: "green",
50:       healthpoints: 30,
51:     },
52:     {
53:       name: "yoshio",
54:       color: "blown",
55:       healthpoints: 33,
56:     },
57:     {
58:       name: "momo",
59:       color: "pink",
60:       healthpoints: 9,
61:     },
62:   ]);
63: 
64:   const query = await db.heroes.find();
65:   const resHealth = await query.sort('healthpoints').exec();
66:   for (let lp = 0; lp < resHealth.length; lp++) {
67:     console.dir(resHealth[lp].get('name'));
68:   }
69:   console.log();
70:   const resColor = await query.sort('color').exec();
71:   for (let lp = 0; lp < resColor.length; lp++) {
72:     console.dir(resColor[lp].get('name'));
73:   }
74: 
75:   await query.remove();
76: }
77: fn();


難しすぎでは!?と思ったが、SQL を使うタイプだと SQL文というテキストになっているだけであんまり変わらんか。

addCollections() の戻り値はコレクションではあるのだが、コレクションの集合のようなものが返ってくるようだ。
これに find() したかったら、今回だと .heroes.find() のように名前を挟まないといかん。
そうなると db.heroes.find() と変わらん気がする。

 

それとは別に気になったのは、RxDB はその下で動いているデータベースを隠蔽するような形で動いていると思うのだが、トランザクションがあるデータベースだとパフォーマンスが悪くならないだろうか。 bulkInsert() で全部放り込めるなら良いけれども、取得しては保存、取得しては保存、みたいな使い方をしたい場合もあるだろうし。

2021年6月19日

[js]非同期とコールバックとPromise

JavaScript で検索するとたくさん出てくる「コールバック Promise」。
この記事もその1つになるのだろう。自分の考えを整理するのに、どうしても文章に書かざるを得ない人たちの一人なので仕方がない。
ちなみに、ブラウザでの動作は考えておらず、Node.js から使うものとして書いている。


JavaScript はシングルスレッドだと言われている。仕様ではここになるのかな?

8.6 Agents
https://262.ecma-international.org/11.0/#sec-agents

An agent comprises a set of ECMAScript execution contexts,
an execution context stack,
a running execution context,
an Agent Record,
and an executing thread.
Except for the executing thread, the constituents of an agent belong exclusively to that agent.

【DeepL翻訳】
エージェントは、ECMAScript の実行コンテキストのセット、実行コンテキストスタック、実行中の実行コンテキスト、エージェントレコード、および実行スレッドから構成されます。実行スレッドを除き、エージェントの構成要素はそのエージェントにのみ属します。

実行しているコンテキストではスタックとかスレッドとかで構成されるけど、全部 a や an が付いているので1つしかないということなんじゃなかろうか。
「single thread」という単語は見つけられなかったのだよ。

解釈は間違ったかもしれんが、とりあえずシングルスレッドであることは信用しよう。


シングルスレッドなのに非同期ってどういうことじゃ、と思ったが、ここは割込みができるシステムと考えればよいと思う。

 

組み込み開発ではよくあることだが、CPU は実行状態として「通常モード」と「特権モード」のようなモードというか状態というかを持つことがある。電源を入れて起動直後は特権モードで、その状態じゃないとできない設定をいろいろやってから通常モードになって動き始める、みたいな感じだ。

通常モードで動いているけれども、例えば外部のセンサからINPUTピンに対して信号が来た場合には割り込んで良いようにする、という設定にしておくと、信号が来たら現在の状態をスタックに保存してから特権モードに切り替わり、特権モードで処理をして終わったらまた通常モードに戻る、というようなことができる。

こういう特権モードになるときは CPUとしてまるまる切り替わるので、スレッドとかそういうのはない。そして特権モードでの動作は通常モードとは異なるのでコンテキストが異なる。すなわち非同期だ。

 

私の場合は JavaScript の非同期をそんな世界観で眺めている。


非同期なところまではよいのだが、非同期で処理を何かしたいことがほとんどだろう。 setTimeout() みたいな遅延動作なんかはわかりやすい例で、何か動作を遅延させてから実行したいのだ。

しかし、実行し始めたコンテキストとは別のコンテキストで動作するので、素直に続きを行うわけにはいかない。コンソールにログを出す、みたいな影響を与えないものであれば気にしなくてよいのだが、変数に値を代入したりファイルに書き込んだりしたいことが多かろう。

グローバル変数みたいにスコープが広い変数であれば代入できると思うが、ローカル変数に戻したいという場合もあろう。そういうときは実行したい単位を関数にしてしまい、Promise<戻り値>を戻すようにして、asyncを付けておくと、呼び出し元はその関数を await つけて呼び出せば

 

例えば、このコードをそのまま実行すると、setTimeout()したログは一番最後に出力される。

01: console.log('hello');
02: setTimeout(() => { console.log('meme'); }, 1000);
03: console.log('world');

hello
world
meme

 

setTimeout()の後にworldを出したいだけなら、こんな感じか。
ちなみに TypeScript で書いていて、 tsconfig.json の "target" は "es2015" にしている。 tsc --init で作られたそのままだと怒られてダメだったのだよね。
vscode で実行していたのでデバッグ設定をしていたのだが、"Launch Program" で実行しても console.log の出力がどこにも出てこなくて悩んだ。実行するとタブが "TERMINAL" になるので、急いで "DEBUG CONSOLE"にすると出てきた。

01: async function setTimeoutPromise(msec: number): Promise<void> {
02:     return new Promise<void>((resolve: any, reject: any) => {
03:         setTimeout(() => { resolve();}, msec);
04:     });
05: }
06: 
07: const fn = async () => {
08:     console.log('hello');
09:     await setTimeoutPromise(1000);
10:     console.log('world');
11: };
12: fn();

なんかめんどくさいね。
await を使いたいがために async の関数が2つ必要になってしまった。

 

それはともかく、setTimeout() の第1引数は関数を受け取るタイプで、指定した時間後にこの関数を呼んでくれる。つまりコールバックだ。
これを呼び出し元で同期に見せかけたいので、Promise を使ってコールバックで resolve() を呼び出すようにしている。

 

自分で作った関数だけで非同期が発生することはなくて、そこからsetTimeout() なり npm でインストールしたライブラリなり、なんらかの外部に依存する場合しか非同期になることはないだろう。そして JavaScript で提供された関数を呼ばないで済むことはまずないので(たぶん変数の参照や代入だけか?)、前提条件はあまり考えなくて良いだろう。

 

非同期の基本的な処理完了通知はコールバックのはず。ただ、組み込み CPUでの割り込み処理で特権モードになったままになるのではなく、Node.js のコンテキストに戻されるはずだから、pthread の join で待って、戻ってきたらコールバック関数を呼び出す、みたいなことをやっているのか。

Promiseはその先の話で、いろいろ書き方はあるのだろうが、

  1. Promise を new するときにコールバックで結果を返す関数を最後に呼び出す関数を登録する
    1. 登録する関数の引数は (resolve, reject) にするのが一般的。
    2. コールバックが呼ばれるときに resolve() を呼び出す(失敗だったら reject() を呼び出す)。
    3. コールバックで結果を得てそれを次も使いたい場合は resolve() の引数にする。
  2. promise.then() を呼び出して、引数は resolve() の中身を実装するようなイメージの関数にする。
01: function setTimeoutPromise(message: string, msec: number): Promise<string> {
02:     return new Promise<string>((resolve: any, reject: any) => {
03:         setTimeout(() => { resolve(message);}, msec);
04:     });
05: }
06: 
07: console.log('hello');
08: setTimeoutPromise('world', 1000).then((msg: string) => {
09:     console.log(msg);
10: });

setTimeoutPromise()の引数をコールバック関数内の resolve()に渡せているのが面白いところだ。全然コンテキストが異なるならアクセスできないはずなので、うまいことやってるのだろう。深く探る必要もあるまい。

そして setTimeoutPromise() に async をつけて、呼び出すときに await をつけると、resolve() の引数がそのまま戻り値として使えるようになる。つまり同期になる。

 

なので、もうあまり難しいことは覚えずに async - await だけ覚えておけばいいかな、と思っていたのだ。


しかし、パフォーマンスを出さないといけないようになると、全部同期で済ませるのはもったいないこともある。非同期になる理由が「忙しいから」であれば待っていても良いのだが、「待ち時間が発生するため」の場合には、その間に他のことをやってしまいたいからだ。

 

まず、Promise.all()というものがある。

Promise.all() - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Promiseのインスタンスを配列で受け取って、then でも配列で結果を返している。面白い。
これを async - await で書くとこんな感じか。

01: const promise1 = Promise.resolve(3);
02: const promise2 = 42;
03: const promise3 = new Promise((resolve, reject) => {
04:     setTimeout(resolve, 100, 'foo');
05: });
06: 
07: const fn = async () => {
08:     const result = await Promise.all([promise1, promise2, promise3]);
09:     console.log(result);
10: }
11: 
12: fn();
13: 

all()以外にもいろいろある。

順番にやっていかないといけないものは async - await でつなげれば良いし、特に順番はどうでもよいなら Promise.all() でまとめてしまえばよい。本当にどうでもよいならコールバック関数が呼ばれておしまい、でよいのだが、そうもいかんことが多かろう。

 

これでなんとかなるかと思っているのだが、どうなんだろうね。苦労してみないと分からんのだろう。

2021年5月2日

[js]はてな、はてなはてな、はてなドット

C言語の人なので、JavaScriptのコードもなんとなく読める。

と思っていたのだが、さっぱりわからんものも存在する。


??, ??=

Null合体、Null合体代入、などと呼ぶらしい。

??

??=

 

まず代入しない方の??から。

A ?? Bという書き方で、Aがnullでもundefinedでもない場合はAを返し、そうでない場合はBを返す。主な使い方なのかわからんが、初期値の設定として使う例が書かれている。

C = A ?? 固定値

みたいな書き方で、Aが初期化済みだったらその値を使い、そうじゃなかったら固定値を割り当てる。

??=は、その代入版のようなものだが、代入されるのは自分が未初期化の場合のみだそうな。


||=, &&=

上の??=に似ていて、条件が合ったときだけ代入される。

||=は自分が偽の場合、&&=は自分が真の場合。


?.

Optional chaining、というそうだ。

接続されたオブジェクトチェーンの深くに位置するプロパティの値を、チェーン内の各参照が正しいかどうかを明示的に確認せずに読み込むことを可能にします

わからんね。

 

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);

これだと、adventurerのメンバにdogはいないのだが、参照の正しさを確認しないので、dogNameに代入するときに「adventurer.dog.nameはundefinedだ」ということでundefinedになる。

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog.name;
console.log(dogName);

この場合は「adventuere.dog自体が存在しない」と代入時に判断するので、console.log()までたどり着かずにエラーとなる。

 

const dogName = adventurer?.dog;
console.log(dogName);

こんな感じでルート項目というか親というかがundefinedの場合は?.であってもエラーになる。参照をチェックしないのはチェーンなので、親はチェックするということだろう。

…などと思ったが、?.の対象となる要素は右側にある名前だ。A?.BだったらBの方に意味があるので、ここだとadventurerの中にdogがあってもなくてもエラーにしないというだけのことだ。

 

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog?.name;
adventurer.dog = {name: 'Tama'};
console.log(dogName);

これは?.とは関係ないのだけど、出力は”Pochi”になった。adventurer.dog.nameはプリミティブな文字列型なのでdogNameに直接代入されたためだろう。adventurer.dogはオブジェクトなので、そっちを代入したなら出力は変更される。

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog;
adventurer.dog.name = 'Tama';
console.log(dogName.name);

 


他にもあるのだろうが、気付いたのはこんなところだ。