Skip to content

Instantly share code, notes, and snippets.

@kaze
Last active February 11, 2020 21:10
Show Gist options
  • Save kaze/9e3b75eb3dbc43e8f890910132d428fd to your computer and use it in GitHub Desktop.
Save kaze/9e3b75eb3dbc43e8f890910132d428fd to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
class APIError extends Error {
constructor (response, details = {}) {
super(`Received response with ${response.status} status code when asking for ${response.url}`);
this.name = "APIError";
this.response = response;
this.details = details;
}
}
const build_url = (endpoint) => {
return `http://localhost:8090/api/v4${endpoint}`;
}
const build_request_params = (ctx) => {
let url = build_url(ctx.url);
let options = {
method: ctx.method,
headers: ctx.headers ? ctx.headers : { "content-type": "application/json" }
};
if (ctx.data && ["GET", "HEAD"].includes(ctx.method.toUpperCase())) {
let params_array = Object.entries(ctx.data);
let divider = item => {
return item === params_array[0] ? "?" : "&";
};
let params = params_array.reduce((acc, item) => {
return `${acc}${divider(item)}${item[0]}=${item[1]}`;
}, "");
url = `${ctx.url}${params}`;
} else if (ctx.data) {
options.body = JSON.stringify(ctx.data);
}
return { url, options };
};
const request = async (ctx) => {
try {
let { url, options } = build_request_params(ctx);
let response = await fetch(url, options);
if (response.ok) {
return Promise.resolve(await response.json());
} else if (response.status === 401) {
throw new APIError({
status: response.status,
url: response.url,
json: async () => {
return [{ code: 'error_request_unauthenticated' }]
}
});
} else {
throw new APIError(response);
}
} catch (err) {
try {
let data = await err.response.json();
let item = data[0];
return Promise.reject(item.code);
} catch (e) {
console.log(err);
return Promise.reject("error_request_service_unavailable");
}
}
};
const create_machine = (id, config, options, context) => {
return Machine(Object.assign({}, {
id: id,
context: context || {},
initial: 'idle',
states: {
idle: {}
}
}, config), Object.assign({}, {
actions: {},
activities: {},
guards: {},
services: {}
}, options));
};
const create_request_machine = (id, options, context) => {
return create_machine(id, {
id: id,
initial: 'idle',
context: Object.assign({}, {
url: null,
method: 'get',
headers: null,
timeout: null,
max_retries: null,
retries: null,
data: null,
results: null,
errors: null
}, context),
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
invoke: {
id: 'fetch',
src: (ctx, event) => request(ctx),
onDone: {
target: 'success',
actions: 'update_results'
},
onError: {
target: 'failure',
actions: 'update_errors'
}
}
},
success: {
type: 'final'
},
failure: {
on: {
RETRY: [
{
target: 'loading',
actions: 'update_retries',
cond: 'can_retry'
}, { target: 'failed' }]
},
},
failed: {
type: 'final'
}
}
}, Object.assign({}, {
actions: {
update_retries: assign({
retries: (ctx, event) => ctx.retries + 1
}),
update_results: assign({
results: (ctx, event) => event.data || null
}),
update_errors: assign({
errors: (ctx, event) => event.data || null
})
},
guards: {
can_retry: (ctx, event) => {
if (ctx.retries && ctx.max_retries) {
return ctx.retries <= ctx.max_retries;
}
return true;
}
}
}, options));
};
const config_loader_machine = create_request_machine(
'api-request',
{},
{ url: '/config', method: 'get', retries: 0, max_retries: 3 }
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment