nodegui/svelte-nodegui

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:

  1. 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.
  2. 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.