weird things happening with JS modules and fetch

Tried creating an “Api” module for my application, which executes api calls whenever I tell it to. Ran into an issue when trying to move fetch inside that module.

Probably there is something wrong with the way I’m creating modules, or using the module.exports, or with my expectations on how the Promises returned by fetch should behave.

Anyway, there’s one small test app with 3 testable cases - one okay and two not so much. Some help, advice on module structure or fetch usage would be appreciated.

.unoproj:

{
  "RootNamespace":"",
  "Packages": [
        "Fuse.Animations",
        "Fuse.BasicTheme",
        "Fuse.Themes",
        "Fuse.Controls",
        "Fuse.Designer",
        "Fuse.Drawing",
        "Fuse.Drawing.Primitives",
        "Fuse.Effects",
        "Fuse.Elements",
        "Fuse.Entities",
        "Fuse.Gestures",
        "Fuse.Navigation",
        "Fuse.Scripting",
        "Fuse.Shapes",
        "Fuse.Triggers",
        "Fuse.Reactive",
        "Fuse.Android",
        "Fuse.Desktop",
        "Fuse.iOS",
        "Fuse.UserEvents",
        "FuseCore",
        "Uno.Collections",
        "Uno.Geometry"
  ],
  "Includes": [
    "*",
    "js/*.js:Bundle"
  ]
}

.ux

<App Theme="Basic">
    <JavaScript File="main.js" />
    <ClientPanel Background="#333" />
</App>

main.js

var App = require('js/App');
App.init();
App.Api.call();

js/App.js

var Observable = require('FuseJS/Observable');

function App() {
};

App.prototype.init = function() {
    this.Api = require('./Api');
};

module.exports = new App();

js/Api.js

var Observable = require('FuseJS/Observable');

/*
uncomment and comment the test-case blocks as needed to test each of them
only leave one active at a time, since they represent the whole class
*/

/*
** Test case 1: ALL GOOD
** 1. calling existing json api
** 2. printing out response
** 3. then returning response.json()
** 4. then getting and printing a valid responseObject
*/

function Api() {
    this.host = 'http://jsonplaceholder.typicode.com';
    //this.host = 'http://nonexistant.host';
};

Api.prototype.call = function() {
    var uri = this.host + '/posts/21';
    console.log('calling: ' + uri);
    fetch(
        uri,
        {}
    )
    .then(function(response) {
        console.log('response: ' + JSON.stringify(response));
        //console.log('response: ' + JSON.stringify(response.json()));
        return response.json();
    })
    .then(function(responseObject) {
        console.log('responseObject: ' + JSON.stringify(responseObject));
    }).catch(function (error) {
        console.log('error: ' + JSON.stringify(error));
    });
};

/*
** END OF Test case 1
*/



/*
** Test case 2: NOT GOOD
** 1. calling non-existing json api
** 2. expecting to get a meaningful error in .catch, but there is only empty object {}
*/
/*
function Api() {
    //this.host = 'http://jsonplaceholder.typicode.com';
    this.host = 'http://nonexistant.host';
};

Api.prototype.call = function() {
    var uri = this.host + '/posts/21';
    console.log('calling: ' + uri);
    fetch(
        uri,
        {}
    )
    .then(function(response) {
        console.log('response: ' + JSON.stringify(response));
        //console.log('response: ' + JSON.stringify(response.json()));
        return response.json();
    })
    .then(function(responseObject) {
        console.log('responseObject: ' + JSON.stringify(responseObject));
    }).catch(function(error) {
        console.log('error: ' + JSON.stringify(error));
    });
};
*/
/*
** END OF Test case 2
*/



/*
** Test case 3: NOT GOOD
** 1. calling existing json api
** 2. trying to print response.json(), a supposed promise (is it?) but it looks like an Observable
** 3. expecting to proceed to .then, but instead hitting .catch with empty object again
*/
/*
function Api() {
    this.host = 'http://jsonplaceholder.typicode.com';
    //this.host = 'http://nonexistant.host';
};

Api.prototype.call = function() {
    var uri = this.host + '/posts/21';
    console.log('calling: ' + uri);
    fetch(
        uri,
        {}
    )
    .then(function(response) {
        //console.log('response: ' + JSON.stringify(response));
        console.log('response: ' + JSON.stringify(response.json()));
        return response.json();
    })
    .then(function(responseObject) {
        console.log('responseObject: ' + JSON.stringify(responseObject));
    }).catch(function(error) {
        console.log('error: ' + JSON.stringify(error));
    });
};
*/
/*
** END OF Test case 3
*/



module.exports = new Api();

This sounds like a regression. Have to do some testing here and get back to you.

Thanks Anders. Apart from the funny module-linking I have, the most interesting case is number 2, where the error variable is nothing but an empty object.

I have changed my approach to how I’m working with modules since the reports, but the fetch problem still remains.

Made a small testcase for just the fetch part that throws an empty error object. Note that this testcase does not need any JS includes or anything, just put the JS code in main.js and include it from UX. Change the host to get either the responseObject, or the error. I’d expect the error to be “Could not connect to host” or something similar in this case, but it’s just {}.

Running Fuse 0.12.4 build 6406 on a Mac, Local preview.

//var host = 'http://jsonplaceholder.typicode.com';
var host = 'http://nonexistant.host';

var uri = host + '/posts/83';
console.log('calling: ' + uri);

fetch(
    uri, {}
).then(function(response) {
    return response.json();
}).then(function(responseObject) {
    console.log('responseObject: ' + JSON.stringify(responseObject));
}).catch(function (error) {
    console.log('error: ' + JSON.stringify(error));
});

The problem is that you are trying to stringify the Error object. The properties on the Error object are listed as enumerable: false so it will not be serialized. If you try the following, you should get the correct message in your log:

}).catch(function (e) {
  console.log(e instanceof Error);
  console.log(e.message);
  console.log(e.name);
  console.log(e.stack);
});

good to know, thanks. tested, works just fine.