How to fetch data with an async call ?
mperreir opened this issue · 5 comments
Hello,
I'm struggling at making a simple thing work : async data fetch. The documentation (https://svelte.nodegui.org/docs/guides/networking) explains how to write a function that make this async call, but not how to call it.
I've tried calling this async function directly in the (async) event handler of a button but the app crashes with FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope.
If I try to use svelte await blocks like this :
<script lang="ts">
import { onMount } from "svelte";
import type { NSVElement, RNWindow } from "@nodegui/svelte-nodegui";
import { Direction } from "@nodegui/nodegui";
import fetch from "node-fetch";
/**
* The exact type for this is: NSVElement<RNWindow>
* ... However, the Svelte language tools erroneously expect all bind:this values to extend HTMLElement, so
* for now, we have to rely on reasserting the type.
*/
let win;
let urlWidget;
let dataPromise = loadData("https://reqres.in/api/products/3");
function loadPressed(){
dataPromise = loadData(urlWidget.textContent);
}
async function loadData(url){
try{
let response = await fetch(url);
let jsonResponse = await response.json();
return jsonResponse;
}
catch(error){
console.error(error);
}
}
onMount(() => {
(window as any).win = win; // Prevent garbage collection, otherwise the window quickly disappears!
(win as NSVElement<RNWindow>).nativeView.show();
return () => {
delete (window as any).win;
};
});
</script>
<window
bind:this={win}
windowTitle="Seafile Share link DL">
<view class="vertical">
<view class="horizontal">
<text>Share link url:</text>
<lineEdit id="lineEdit" bind:this={urlWidget}/>
<button text="Load" on:clicked={loadPressed}/>
</view>
<view>
{#await dataPromise}
<text>Nothing loaded</text>
{:then data}
<text>{data}</text>
{:catch error}
<text>{error.message}</text>
{/await}
</view>
</view>
</window>
<style>
/*
* CSS has a few gotchas for now.
* 1) Some values need to be enclosed with quotes (e.g. `width: '100%';` rather than `width: 100%;`).
* See: https://github.com/nodegui/svelte-nodegui/issues/4
* 2) Classes are not supported yet; they're a bit weird in Qt5.
See: https://github.com/nodegui/svelte-nodegui/issues/6
* 3) You can't write element-level rules like `text { color: 'red'; }`, unless they're global (not scoped).
* For scoped rules, you have to refer to the underlying native element, e.g. `QLabel { color: 'red'; }`.
* See: https://github.com/nodegui/svelte-nodegui/issues/7
*/
.vertical{
flex-direction: column;
}
.horizontal{
flex-direction: row;
}
#lineEdit{
flex: 1;
}
</style>
then I get a UnhandledPromiseRejectionWarning: ReferenceError: __awaiter is not defined...
What is the correct way to do ?
Looks correct at a glance. What is your version of Node? I'm wondering whether it lacks support for await. Or maybe we've got some bad Babel settings configured.
I'm noticing this build-time Webpack error, "Can't resolve 'encoding'", when I install node-fetch and reproduce your code:
$ npm run dev
> @1.0.0 dev /Users/jamie/Documents/git/svelte-nodegui/demo
> webpack --mode=development
asset nodegui_core-5e470ea461e784c6379553449d91d9e9.node 7.17 MiB [emitted] (auxiliary name: main)
asset nodegui_core-8513d0bc5d20bdecaa7e359473bb2364.node 7.16 MiB [emitted] (auxiliary name: main)
asset index.js 2.79 MiB [emitted] (name: main) 1 related asset
runtime modules 21.9 KiB 10 modules
cacheable modules 2.44 MiB
modules by path ./ 1.19 MiB 259 modules
modules by path ../ 1.25 MiB 258 modules
external "url" 42 bytes [built] [code generated]
external "http" 42 bytes [built] [code generated]
external "https" 42 bytes [built] [code generated]
external "zlib" 42 bytes [built] [code generated]
external "stream" 42 bytes [built] [code generated]
external "events" 42 bytes [built] [code generated]
external "os" 42 bytes [built] [code generated]
external "path" 42 bytes [built] [code generated]
external "crypto" 42 bytes [built] [code generated]
external "querystring" 42 bytes [built] [code generated]
external "fs" 42 bytes [built] [code generated]
WARNING in ./node_modules/node-fetch/lib/index.mjs 156:11-38
Module not found: Error: Can't resolve 'encoding' in '/Users/jamie/Documents/git/svelte-nodegui/demo/node_modules/node-fetch/lib'
@ ./src/App.svelte 22:0-31 205:25-30
@ ./src/app.ts 2:0-31 3:14-17
1 warning has detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.
I also got the same runtime error:
(node:5605) UnhandledPromiseRejectionWarning: ReferenceError: __awaiter is not defined
I wonder whether they're connected. The encoding module is an optional dependency, which is imported via require(), which makes it a little odd, and I wonder whether an extra Webpack plugin is needed for it or something. We could also simply install encoding and see how it goes, of course.
I did get the code to work, however, by giving up on async/await as a workaround:
<script lang="ts">
import { onMount } from "svelte";
import type { NSVElement, RNWindow } from "@nodegui/svelte-nodegui";
import { Direction } from "@nodegui/nodegui";
import fetch from "node-fetch";
/**
* The exact type for this is: NSVElement<RNWindow>
* ... However, the Svelte language tools erroneously expect all bind:this values to extend HTMLElement, so
* for now, we have to rely on reasserting the type.
*/
let win;
let urlWidget;
let urlWidgetTextContent: string = "https://reqres.in/api/products/3";
let dataPromise: Promise<any> | undefined;
function loadPressed(): void {
dataPromise = loadData(urlWidget.textContent);
}
/**
* async/await doesn't appear to be working. This is probably due to missing a Webpack setting of some sort.
* For the time being, we can write old-school Promises instead.
* We can still use await blocks in the Svelte HTMLX markup as that seems to work in a different way.
*/
function loadData(url: string): Promise<any> {
return fetch(url)
.then((response) => {
return response.json();
})
.catch((error) => {
console.error(error);
throw error;
});
}
onMount(() => {
(window as any).win = win; // Prevent garbage collection, otherwise the window quickly disappears!
(win as NSVElement<RNWindow>).nativeView.show();
dataPromise = loadData(urlWidgetTextContent);
return () => {
delete (window as any).win;
};
});
</script>
<window
bind:this={win}
windowTitle="Seafile Share link DL">
<view class="vertical">
<view class="horizontal">
<text>Share link url:</text>
<lineEdit id="lineEdit" bind:this={urlWidget}>
{urlWidgetTextContent}
</lineEdit>
<button text="Load" on:clicked={loadPressed}/>
</view>
<view>
{#await dataPromise}
<text>Nothing loaded</text>
{:then data}
<text>{JSON.stringify(data, null, 4)}</text>
{:catch error}
<text>{error.message}</text>
{/await}
</view>
</view>
</window>
<style>
/*
* CSS has a few gotchas for now.
* 1) Some values need to be enclosed with quotes (e.g. `width: '100%';` rather than `width: 100%;`).
* See: https://github.com/nodegui/svelte-nodegui/issues/4
* 2) Classes are not supported yet; they're a bit weird in Qt5.
See: https://github.com/nodegui/svelte-nodegui/issues/6
* 3) You can't write element-level rules like `text { color: 'red'; }`, unless they're global (not scoped).
* For scoped rules, you have to refer to the underlying native element, e.g. `QLabel { color: 'red'; }`.
* See: https://github.com/nodegui/svelte-nodegui/issues/7
*/
.vertical{
flex-direction: column;
}
.horizontal{
flex-direction: row;
}
#lineEdit{
flex: 1;
}
</style>I found the issue. Apparently we should set "noEmitHelpers": false in the tsconfig.json for our app in order for async/await to work correctly. Here's a working config:
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"module": "esnext",
"declaration": true,
"removeComments": true,
"noLib": false,
"jsxFactory": "svelteNodeGUI.createElement",
"emitDecoratorMetadata": false,
"experimentalDecorators": true,
"pretty": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"noEmitHelpers": false,
"noEmitOnError": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noImplicitUseStrict": false,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"lib": ["es6", "dom", "es2015.iterable", "es2017.string", "es2018.promise"],
"baseUrl": ".",
"types": ["node", "svelte"],
"paths": {
"~/*": ["src/*"],
"*": ["./node_modules/*"]
}
},
/**
* `svelte-nodegui.d.ts` provides the JSX typings needed to support Svelte components using lang="ts".
* If you'd prefer not to introduce a "files" property into your tsconfig, you can add this
* triple-slash directive to the top line of `src/app.ts` instead:
* /// <reference path="../node_modules/@nodegui/svelte-nodegui/svelte-nodegui.d.ts" />
* Remember to restart the Svelte Language Service after any significant changes to configuration.
*/
"files": ["./node_modules/@nodegui/svelte-nodegui/svelte-nodegui.d.ts"],
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}There are some other issues that have surfaced from this:
- If you wrap the whole
{#await}...{/await}block with an{#if dataPromise}, the enclosed text elements fail to update, with reasons relating to the next point. - The console is spamming
addChild() called on an element that doesn't implement nodeOps.remove() [ undefined ]. This is something to do with updating the child text nodes passed into the<text>element. That's a bug with the text-updating logic, though somehow we get away with it in this example. I'll track it.
Thank you very much, now it works as intended 😄 !
I'll continue testing svelte-nodegui.