kjda / ReactFlux

A library implementing React-Flux data flow design pattern + Code Generation
MIT License
66 stars 12 forks source link

Error callback not called #18

Open LKay opened 9 years ago

LKay commented 9 years ago

I mentioned this problem in issue #14 but couldn't figure out the fix yet. So here is my example code:

example.js

"use strict";

var React     = require("react"),
    ReactFlux = require("react-flux");

var constants = ReactFlux.createConstants([
    "FETCH",
], "RESOURCE");

var actions = ReactFlux.createActions({
    fetch: [constants.FETCH, function (params) {
        return $.ajax({
            url: "/error_call",
            error: function () {
                console.log("error callback for $.ajax"); // This gets called every time.
            }
        });
    }]
});

var store = ReactFlux.createStore({
    getInitialState: function () {
        return {
            isLoading: false
        };
    }
}, [
    [constants.FETCH, function onFetch () {
        console.log("onFetch"); // This gets called every time.
        this.setState({
            isLoading: true
        });
    }],

    [constants.FETCH_ERROR, function onFetchError () {
        console.log("onFetch Error"); // This gets called only on first fail.
    }],

    [constants.FETCH_AFTER, function onFetchAfter () {
        console.log("onFetch After"); // This gets called only on first fail.
        this.setState({
            isLoading: false
        });
    }]
]);

var Component = React.createClass({
    mixins: [ store.mixin() ],

    getStateFromStores: function () {
        return {
            request: store.state
        };
    },

    render: function () {
        return (
            <button onClick={this._onClickButton} disabled={this.state.request.get("isLoading")}>Send Request</button>
        );
    },

    _onClickButton: function () {
        actions.fetch();
    }
});

React.render(<Component />, document.body);

Let's say I have a API route which returns error for some reason (404, 403, 500, whatever). In that case after the first call when the request fails Flux store listener calls onFetchError and onFetchAfter callbacks but when I try to click the button (send request) again without refreshing the page the request is being sent (callback onFetch called) but then none of the onFetchError and onFetchAfter is being called when another error occurs. The $.ajax error callback is called every time. Looks like on the first try the error exception is beiing muted and on next calls the state for that is not cleared.

kjda commented 9 years ago

Hi, sorry for the late answer! I still could not find time to look at this...

but I think you can solve it using Promises
you could use something like this

function ApiCall(type, endoint, data){
    return new Promise(function(resolve, reject){
        $.ajax({
            url: endpoint,
            dataType: "JSON"
            //..... More options
        }).done(function(resp) {
            //Assuming your server sends a response of type  "{success: bool, error: string, data: {}}" 
            if (resp.success !== true ) {
                reject(new Error(resp.error));
            }else{
                resolve(resp.data);
            }
        })
        .fail(function(jqXHR, textStatus, errorThrown ){
            reject(new Error("server.unreachable"));
        }); 
    });
}

and

    //in  action
    fetch: [constants.FETCH, function (params) {
        return ApiCall("GET",  "/error_call")
    }],

    //In store
    [constants.FETCH_SUCCESS, function onFetchSuccess (payload) {
        console.log(payload)
    }],

    [constants.FETCH_ERROR, function onFetchError (error) {
        console.log(error.message)
    }],
LKay commented 9 years ago

Ok, using Promise lib it solved the problem but it's not quite perfect. $.ajax already returns promise object and looks like it responds correctly to react-flux flow unless it raises an error. Wrapping in yet another promise object should not be necessary.

LKay commented 9 years ago

Even with Promise weird thing happens. This code works:

return new Promise(function (resolve, reject) {
            $.ajax({
                url: "/error_call"
            }).done(function (response) {
                resolve(response)
            }).fail(function (jqXHR, textStatus, errorThrown) {
                reject();
            });
        });

But this doesn't:

return Promise.resolve($.ajax({
            url: "/error_call"
        }));

Yet according to docs both are the same https://www.promisejs.org/patterns/

kjda commented 9 years ago

I think this has to do with the way jquery implements promises... I need to investigate this in more details