-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
repl: allow await
in REPL
#13209
Comments
👍 I looked into implementing this when Node 7.6.0 came out and async functions were originally added. At first glance it seemed like it would be a bit difficult because the REPL currently doesn't run in a function context at all. However, there might be a better solution that I'm not aware of. |
I'm +1. |
|
I'm not arguing that this is a beneficial feature. But IMHO the REPL is an exploratory tool, so those tools at least enable easier exploration 🤷♂️ |
The question is, is it really possible? It seems like it's not |
It would definitely be possible in theory by reimplementing |
There was a feature request issue for this and there are some solutions proposed there: #8382 although it seems most of them are somewhat hacky |
Why can't we just wrap the content with an async function? |
@benjamingr, that will change the semantics of repl. We need to detect it somehow, or use a flag, or force this as a semver-major change for everyone. |
@ChALkeR what semantics would it change? I figured that since the REPL is not synchronous anyway (being user input and all) the microtick of wrapping it in an async function - unwrapping it and giving the user back the result wouldn't be too bad. Am I missing anything? |
@benjamingr do you have ideas about how to make this work: > const a = Promise.resolve(42);
undefined
> const b = await a;
undefined
> b
42 If we wrapped the code in an async function, |
@targos you're absolutely right! I totally missed the scoping issue. Parsing the line and figuring out if it's a variable declaration seems easier than detecting an async function, it would still be problematic for things like I'm wondering if we could have an escape hatch at the V8 level perhaps? Optimally something that lets V8 know it should evaluate the code passed as an async function? Essentially, we'd need a way for I'm wondering if that's possible - @nodejs/v8 @fhinkel ? |
This also needs some kind of cancellation option - like await new Promise(() => {}) // forever pending should not break the REPL |
This seems like an odd idea to me, what does the repl do during an await? Does it just stall and not respond to further user input? How do you know when the await is completed otherwise? Either way seems confusing and poor design to me, maybe I'm not considering something? I suppose it's not much different that running a long sync operation although that is probably less common. |
Yeah, I think stalling is the desired behaviour. The idea is to be able to inspect the return values of a promise API and use them for subsequent operations in a more friendly way. Currently you have to attach then() handlers to write values into global s and check repeatedly to see when they become available. It's very unfriendly to promise users. |
Maybe show a spinner? |
Sounds good. As long as you can see that something is happening and you can abort it with Ctrl+C, we should be good. :) |
Sounds inappropriate for REPL. Aborting is a must, though. That said, I think it's a waste of time to discuss such details until we figure out a way to do this. |
at some point people might want to think of foreground and backgrounding await if you need to test how 2 async things interact... like Jobs in shells |
Isn't there a key for that in |
FWIW that's what regenerator already did (as used by Babel for transpiling generators and async-await to ES5). So for a problematic example above, wrapped into an async function: (async function() {
const b = await a;
})(); it would generate (function _callee() {
var b;
return regeneratorRuntime.async(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(a);
case 2:
b = _context.sent;
case 3:
case "end":
return _context.stop();
}
}
}, null, this);
})(); From here, we could easily unwrap the IIFE and execute body, which would solve the scope propagation issue as all the modified variables would be visible after the body is executed. However I'm not sure if we would want to integrate such, even minimal, third-party transpiler into Node.js REPL. But if we do, I could try to make this happen :) |
IMO REPL should be predictable. |
If you can make this happen and the code doesn’t make the node binary turn huge, I would be in favour of doing that. |
I think you should consult some of v8 guys for help and taking best strategy here as the same feature would be nice for Chrome console as well so a similar approach could be used for node.js I guess. (BTW I am very happy that this is proposed here as the idea of top-level await itself seems is rejected but all I personally liked to have was a console one) |
Filed as |
According to this comment and my test, this is already supported on Safari so I guess Chrome team should/will soonish pick up the task to fill the gap up with the other browser. |
thanks for supporting it!. I don't know if it is just me, but in Node |
@esteban-uo It seems you need |
@vsemozhetbyt yes and actually it works just fine 👍 Seems to be it happens when an exception occurs, which is very confusing sometimes, since you could think that the promise can't be resolved at all or a timeout is happening |
Forgive me if I am misreading but according to the above I would be able to CLI node app.js where app.js could contain awaitS placed outside functions ( ex: copy and paste from puppeteersandbox.com examples without wrapping and cli running it ) ? |
@domainoverflow Thank you for asking! This issue is only about using "await" in the special "REPL" mode of Node.js. If you run REPL is a different from how programs run normally. And for JS programs, it is not possible to use await outside async functions because the JavaScript programming language is specified in a way that does not allow this. There is a proposal from TC39 to allow "await" outside functions in normal programs. More about that at https://github.com/tc39/proposal-top-level-await. This was relatively easy to do in REPL for debugging and testing, but is less easy for normal programs. If the proposal is approved, then the JavaScript engine for Node.js (Google V8) may implement the change, after which it may eventually become available in Node.js. |
+1 |
Any way to specify the experimental flag programmatically (instead of command line)? since I'm using repl.start api |
You can try NODE_OPTIONS=options... |
@jiayuzhang in linux |
Is there any specific reason why is |
@filips123 when await is enabled we run input through a separate parser which doesn't necessarily match the way node normally parses code, so there could be incompatibilities. In the long run, a combination of top-level-await and a standardized repl parse goal should fix this. https://github.com/tc39/proposal-top-level-await |
Summary, it works with an experimental flag. use something like the following to load some js and drop you into a repl after the variables are set, kind of like bashrc.
With enough run commands, it might be possible to replace bash with node lol |
Has anyone figured out how to get the experimental flag to work in a REPL that is spawned with
|
Having the same issue as @nahtnam with trying to get await mode turned on from |
I did this and it worked "scripts": {
"repl": "node --experimental-repl-await ./repl.js"
} And in const { readFileToArray } = require("./utils");
const repl = require("repl");
repl.start("> ").context.readFileToArray = readFileToArray; When I run it it works fine and I can await on my function readFileToArray |
Top level await is now supported in V8 (https://v8.dev/features/top-level-await) Also, I could really use |
@tomasgvivo TLA is part of modules. Once we land support for it (#30370), you can use it in a There is another thing V8 is working on called "repl mode" which might help with await in the repl though: https://docs.google.com/document/d/1bIKMRrLVObsAfmMAQ-tmsWkQZu1cFg87RRrPpiwTL2M/edit |
When the adding the command line const defaultEval = myRepl.eval;
myRepl.eval = (cmd, context, filename, callback) => {
defaultEval(cmd, context, filename, async (err, result) => {
if (err) {
// propagate errors from the eval
callback(err);
} else {
// awaits the promise and either return result or error
try {
callback(null, await Promise.resolve(result));
} catch (err) {
callback(err);
}
}
});
}; It is not a general solution but worked well for my use case. |
// index.js
const repl = require("repl");
const myRepl = repl.start();
const defaultEval = myRepl.eval;
myRepl.eval = (cmd, context, filename, callback) => {
defaultEval(cmd, context, filename, async (err, result) => {
if (err) {
// propagate errors from the eval
callback(err);
} else {
// awaits the promise and either return result or error
try {
callback(null, await Promise.resolve(result));
} catch (err) {
callback(err);
}
}
});
}; usage function delayPromise(delay){
//async delay function
return new Promise(resolve => setTimeout(() => resolve(), delay))
}
delayPromise(3000)
undefined
//wait 3 seconds until can proceed typing |
As stated here nodejs/node#13209 (comment) , the feature is available since node ^10. So there should not be any drawbacks for any supported Node version
I'm not sure why, but using import util from "util"
util.inspect.defaultOptions.getters = true
util.inspect.defaultOptions.depth = 4
util.inspect.defaultOptions.maxStringLength = 80 But when using import util from "util"
util.inspect.replDefaults.getters = true
util.inspect.replDefaults.depth = 4
util.inspect.replDefaults.maxStringLength = 80 The docs seem to suggest that the |
diff --git a/articles/9ef3a030cb51de.md b/articles/9ef3a030cb51de.md new file mode 100644 index 0000000..d8a47b2 --- /dev/null +++ b/articles/9ef3a030cb51de.md @@ -0,0 +1,193 @@ +--- +title: "REPL上でのFirestoreのデータ操作をちょっと便利にするOSSを作った" +emoji: "🔥" +type: "tech" # tech: 技術記事 / idea: アイデア +topics: ["Firebase","Firestore","Nodejs","TypeScrip","REPL"] +published: true +--- + +# 何を作った? + +Firestore のデータ内容を確認したり修正したりするときに、いちいち Firebase のコンソール上でぽちぽちやるのが面倒でした。 +だからといって、Node.js の REPL で Firebase Admin SDK を使うと、Admin SDK の初期化が手間だったり Node.js の REPL が標準で Top-Level Await に対応していなかったりとこちらも準備が大変です。 + +そこで、Node.js の REPL を拡張して、Firestore のデータ操作を簡単に行えるカスタム REPL を作り OSS として公開してました。 + +名前は `Firepl`(Firebase + REPL)🔥 + +https://github.com/kawamataryo/firepl + +Firebase のサービスアカウントを指定して起動すると、Firestore の初期化を自動で行うので、REPL 上にて`firepl.db` ですぐ Firestore を操作できます。さらに `firepl.getData` を使うと少し面倒な snapshot の取り回しを抽象化して目的のデータを取得できます。 +Node.js 標準 REPL の薄いラッパーなので、データの修正・加工も素の JS で手軽に行えます。最高便利。 + +![](https://i.gyazo.com/b40500fc08082a28bbf42c263f741766.gif) + +# 使い方 + +最初に Firebase Admin SDK の初期化に使うサービスアカウントを Firebase のコンソールから取得します。 + +`プロジェクトの設定` > `サービスアカウント` でアクセスできます。 + +![](https://i.gyazo.com/10b159f02c8703ecfdc39d24f8267f81.png) + + +ダウロードしたサービスアカウントを適当な場所に配置して、そのパスを firepl の起動時引数に与えると起動します。 + +```bash +# npx で使う場合 +$ npx firepl -c path/to/serviceAccountKey.json + +# global installして使う場合 +$ npm i -g firepl +$ firepl -c path/to/serviceAccountKey.json +``` + +起動した REPL 内にて`firepl.db`でサービスアカウントに指定したプロジェクトの Firestore にアクセス出来ます。コレクションやドキュメントの操作も通常の Admin SDK と同様です。 + +```bash +🔥 > docRef = firepl.db.collection("user").doc("documentId") +``` + +collectionReference または、documentReference の内容を取得したいときは、`firepl.getData`が使えます。snapShot を介さずデータを扱えます。 + +```bash +🔥 > data = await firepl.getData(docRef) +``` + +# 技術的なポイント + +## Node.jsのREPLをそのまま利用する + +当初 REPL を作るのはハードル高そうと尻込みしていたのですが、Node.js の以下ドキュメントページにあるとおり、既存の REPL を拡張することで、とても手軽に独自の REPL を作れました。 + +https://nodejs.org/en/knowledge/REPL/how-to-create-a-custom-repl/ + +REPL 部分はわずか数行です。 + +[📦 src/main.ts](https://github.com/kawamataryo/firepl/blob/main/src/main.ts) + +```ts +#!/usr/bin/env node + +import * as repl from "pretty-repl"; +import { asyncEval } from "./lib/eval"; +import { Firepl } from "./lib/firepl"; +import { getArgs } from "./lib/args"; + +const args = getArgs(); +const firepl = new Firepl(args.credentials); + +console.log("Welcome to Firepl.\nexit using Ctrl+Z or Ctrl+C or type .exit\n"); + +const customRepl = repl.start({ prompt: "🔥 > ", eval: asyncEval }); +customRepl.context.firepl = firepl; +``` + +repl.start で prompt や eval を指定して、ReplServer を作り、その context に REPL 内で使えるグローバルな状態を定義しています。 + +:::message +`repl`の import が`pretty-repl`となっているのは、REPL 内でシンタックスハイライトが効かせようと以下ライブラリを利用しているためです。通常の`repl`でも動作は変わりません。 +https://www.npmjs.com/package/pretty-repl +::: + +この REPL の肝となる Firestore の初期化部分は以下です。 + +[📦 src/lib/firepl.ts](https://github.com/kawamataryo/firepl/blob/main/src/lib/firepl.ts) + +```js +import * as admin from "firebase-admin"; +import { firestore } from "firebase-admin"; +import { join } from "path"; + +export class Firepl { + db: firestore.Firestore; + + constructor(serviceAccountPath: string) { + const serviceAccount = require(join(process.cwd(), serviceAccountPath)); + + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + }); + this.db = admin.firestore(); + } + + async getData( + ref: firestore.CollectionReference | firestore.DocumentReference + ) { + try { + const snapShot = await ref.get(); + if ("docs" in snapShot) { + return snapShot.docs.map((d) => d.data()); + } + return snapShot.data(); + } catch (e) { + console.error(e); + } + } +} +``` + +コマンド引数で指定されたサービスアカウントを利用して`admin.initializeApp()`で Firebase Admin の初期化を行っています。 +この Firepl クラスは前述の REPL の作成時に初期化し REPL の context に設定しています。 + +:::message +「サービスアカウントをライブラリに渡すのは不安。.」という意見もあると思います(自分もあります)。ただ、本当にここでしか利用してないので、安心して使ってください🙏 +::: + +## Top-Level Awaitへの対応 + +通常の Node.js の REPL は Top-Level Await に対応しておらず、async/await なコードを扱う場合、`(async () => { await xxx })()`と即時関数で包む必要がありました。 + +nodejs/node#13209 + +Node.js 10 系から起動時のオプションで`--experimental-repl-await`を指定することで、Top-Level Await を使えるようにはなったのですが、いちいちオプションを付けるのは面倒です。 + +そこで今回は[node-repl-await](https://www.npmjs.com/package/node-repl-await)というライブラリを利用して、REPL の eval を拡張することで Top-Level Await を実現しています。 + +https://www.npmjs.com/package/node-repl-await + + +[📦 src/lib/eval.ts](https://github.com/kawamataryo/firepl/blob/main/src/lib/eval.ts) + +```ts +import { processTopLevelAwait } from "node-repl-await"; +import * as vm from "vm"; +import * as repl from "repl"; +import { Context } from "vm"; + +function isRecoverableError(error: Error) { + if (error.name === "SyntaxError") { + return /^(Unexpected end of input|Unexpected token)/.test(error.message); + } + return false; +} + +export const asyncEval = async ( + code: string, + context: Context, + filename: string, + callback: (...args: any[]) => any +) => { + code = processTopLevelAwait(code) || code; + + try { + const result = await vm.runInNewContext(code, context); + callback(null, result); + } catch (e) { + if (e instanceof Error && isRecoverableError(e)) { + callback(new repl.Recoverable(e)); + } else { + console.log(e); + } + } +}; +``` + +これでいちいち即時関数に包むことなく REPL 上で await が手軽に使えます。 + +# おわりに + +全コードで百数行のとても簡易な OSS ですが、自分的に便利に使えて大満足です。 +何か不具合ありましたら、気軽に Issue や PullRequest を送ってください。 + +また、実は自分が認知していないだけでもっと便利に Firebase のデータを確認したり操作したりするツールもありそうです。何かあればコメントもらえると嬉しいです🙏 \ No newline at end of file diff --git a/articles/jest-mockes-samples.md b/articles/jest-mockes-samples.md new file mode 100644 index 0000000..7948b9e --- /dev/null +++ b/articles/jest-mockes-samples.md @@ -0,0 +1,46 @@ +--- +title: "Jestのmockいろいろ" +emoji: "🍥" +type: "tech" # tech: 技術記事 / idea: アイデア +topics: ["TypeScript", "JavaScript", "Jest", "Test"] +published: false +--- + +# ユーザー定義モジュールのClassのモック +`jest.mock(<module_path>)`でモジュールをモックする。`MockedClass<T>`を使うと楽。 + +```ts +import FooClass from './lib/fooClass' + +// モック化 +jest.mock('./lib/fooClass') +const FooClassMock = FooClass as jest.MockedClass<typeof FooClass> +``` + +## クラスメソッドのモック + +```ts +describe('fooClass', () => { + it('foo', () => { + // クラスメソッドのモック + FooClass.staticMethod = jest.fn().mockResolvedValue(true) + + // assert + }) +}) +``` + +## コンストラクタ関数をモック + +```ts +import FooClass from './lib/fooClass' + +// モック化 +jest.mock('./lib/fooClass', () => { + return { instanceMethod: jest.fn() } +}) + +``` + +# 参考 +https://jestjs.io/ja/docs/es6-class-mocks \ No newline at end of file
diff --git a/.cache/kvs-node-localstorage/proofdict-lastUpdated b/.cache/kvs-node-localstorage/proofdict-lastUpdated index ffd40e2..c55f763 100644 --- a/.cache/kvs-node-localstorage/proofdict-lastUpdated +++ b/.cache/kvs-node-localstorage/proofdict-lastUpdated @@ -1 +1 @@ -1631445873552 \ No newline at end of file +1631446387332 \ No newline at end of file diff --git a/articles/9ef3a030cb51de.md b/articles/9ef3a030cb51de.md index d8a47b2..c24303f 100644 --- a/articles/9ef3a030cb51de.md +++ b/articles/9ef3a030cb51de.md @@ -9,7 +9,7 @@ published: true # 何を作った? Firestore のデータ内容を確認したり修正したりするときに、いちいち Firebase のコンソール上でぽちぽちやるのが面倒でした。 -だからといって、Node.js の REPL で Firebase Admin SDK を使うと、Admin SDK の初期化が手間だったり Node.js の REPL が標準で Top-Level Await に対応していなかったりとこちらも準備が大変です。 +だからといって、Node.js の REPL で Firebase Admin SDK を使うと、Admin SDK の初期化が手間だったり Node.js の REPL が標準で Top-level await に対応していなかったりとこちらも準備が大変です。 そこで、Node.js の REPL を拡張して、Firestore のデータ操作を簡単に行えるカスタム REPL を作り OSS として公開してました。 @@ -134,15 +134,15 @@ export class Firepl { 「サービスアカウントをライブラリに渡すのは不安。.」という意見もあると思います(自分もあります)。ただ、本当にここでしか利用してないので、安心して使ってください🙏 ::: -## Top-Level Awaitへの対応 +## Top-level awaitへの対応 -通常の Node.js の REPL は Top-Level Await に対応しておらず、async/await なコードを扱う場合、`(async () => { await xxx })()`と即時関数で包む必要がありました。 +通常の Node.js の REPL は Top-level await に対応しておらず、async/await なコードを扱う場合、`(async () => { await xxx })()`と即時関数で包む必要がありました。 nodejs/node#13209 -Node.js 10 系から起動時のオプションで`--experimental-repl-await`を指定することで、Top-Level Await を使えるようにはなったのですが、いちいちオプションを付けるのは面倒です。 +Node.js 10 系から起動時のオプションで`--experimental-repl-await`を指定することで、Top-level await を使えるようにはなったのですが、いちいちオプションを付けるのは面倒です。 -そこで今回は[node-repl-await](https://www.npmjs.com/package/node-repl-await)というライブラリを利用して、REPL の eval を拡張することで Top-Level Await を実現しています。 +そこで今回は[node-repl-await](https://www.npmjs.com/package/node-repl-await)というライブラリを利用して、REPL の eval を拡張することで Top-level await を実現しています。 https://www.npmjs.com/package/node-repl-await
I've seen this requested a few times in StackOverflow now:
People have code that is asynchronous and would like to use values from it in the REPL:
A common workaround is to store a global reference in the REPL, which is a little hacky and relies on timing, but can work for simple cases:
This works, but has timing issues and generally isn't great. We could however run the REPL in the context of an async function potentially - which would allow
await
ing values in the REPL:I think it could be an interesting enhancement but would like more feedback on the idea. @nodejs/collaborators
The text was updated successfully, but these errors were encountered: