Skip to content

Instantly share code, notes, and snippets.

@polerin
Last active May 20, 2022 18:35
Show Gist options
  • Save polerin/c7746be5d81bb2eff36ae9a819cbc4ca to your computer and use it in GitHub Desktop.
Save polerin/c7746be5d81bb2eff36ae9a819cbc4ca to your computer and use it in GitHub Desktop.
Example of api client implementations, focusing on contained state through class properties or enclosed scope
// factory included just to be silly
class ApiClientFactory
{
options;
defaultOptions = {
"apiClientClass" : "ApiClientPrecheck",
"clientOptions" : {
"tokenLife" : 400,
"someDeepConfig" : {
"foo" : 10,
"bar" : "baz"
}
}
};
requestFormatters;
credentials = {};
constructor(credentials, requestFormatters, options = {})
{
this.options = {...this.defaultOptions, ...options};
this.credentials = credentials;
this.requestFormatters = requestFormatters;
}
build(options = {})
{
return (this.credentials, this.requestFormatters, {...this.options.clientOptions, ...options});
}
}
/// BINDING Examples
const foo = function(whatever, somthing, other) {
//...
if (whatever === undefined) {
console.log("oh no");
}
if (something === undefined) {
console.log("pain and suffering");
}
if (other === undefined) {
console.log("yelling!");
}
}
const boundFoo = foo.bind(null, "12", "a pizza");
foo(); //logs all three
boundFoo(); //only logs yelling!
boundFoo("asdfasdf"); // logs nothing
const requestFormatterExternal = {
getUsers : function(request) {
},
getLogs : function(request) {
},
__defaultFormatter: function(request) {
}
};
const responseFormatterExternal = {
getUsers : function(request, response) {
},
getLogs : function(request, response) {
}
};
// factory function, returns a composed client function which makes requests
// the outermost auto executing function is mostly just being silly to
// show how you can use enclosed scopes for different phases of execution.
// I don't actually reccomend that outer layer ;)
const getApiClient = ((defaultOptions, requestFormatters, responseFormatters) => {
// returning a constructed function
return (credentials, options = {}) => {
options = {...defaultOptions, ...options};
const token = "";
const refreshToken = () => {
// .. refresh logic
};
const makeRequest = async (requestObject) => {
// blah blah blah check the inline class makeRequest,
// the idea would be the same
}
return async (requestObject) => {
//do the requesting, similar to above
// has access to refreshing
await refreshToken();
};
};
})(
{/* option defaults */},
requestFormatterExternal,
responseFormatterExternal
);
const client = getApiClient(credentials, {"whatever" : "yup"});
const users = await client({'type' : 'getUsers', 'limit': 100});
class ApiClientInlineCheck
{
options;
defaultOptions = {
"tokenLife" : 400,
"maxRequestAttempts" : 5
};
requestFormatters;
responseFormatters;
credentials = {};
token;
constructor(credentials, requestFormatters = {}, responseFormatters = {}, options = {}) {
this.credentials = credentials;
this.options = {...this.defaultOptions, ...options};
this.requestFormatters = requestFormatters;
this.responseFormatters = responseFormatters;
this.checkTokenRequestValidity = this.checkTokenRequestValidity.bind(this);
}
/**
* @throws on unexpected api errors
* @throws on max request attempts
*/
async makeRequest(requestDetails, iteration = 0) {
const formattedRequest = this.formatRequest(requestObject);
// Probably
try {
const response = await makeHttpRequest(formattedRequest);
return this.formatResponse(requestObject, response);
} catch (error) {
if (error.type !== "invalid token") {
throw {'message' : 'Unexpected error in api request', 'error' : error};
}
if (interation >= this.options.maxRequestAttempts) {
throw {'message' : 'Unable to make request, max attempts reached'};
}
}
// fallthrough for token refresh required
await this.refreshToken();
return this.makeRequest(requestDetails, iteration + 1);
}
refreshToken() {
// make your request with credentials
let tokenRequest = {
// your params here, with un/pw I guess?
};
return this.actuallyMakeRequest(tokenRequest)
.then(this.handleTokenResponse);
}
handleTokenResponse(response)
{
// chck validity
this.token = resonse.token;
return somethingCompletelyDiffernt;
}
formatRequest(requestObject) {
const requestType = requestObject.type ?? "unknown";
if (typeof this.requestFormatters[requestType] === 'function') {
return this.requestFormatters[requestType](requestObject);
}
return this.requestFormatters.__defaultFormatter(requestObject);
}
formatResponse(request, response)
{
const requestType = requestObject.type ?? "unknown";
if (typeof this.responseFormatters[requestType] === 'function') {
// binding the request object for later use in the formatter
return this.responseFormatters[requestType](request, response);
}
return {
// default format response
// you can check requestObject because it is in the containing scope
};
}
}
class ApiClientPrecheck
{
options;
defaultOptions = {
"tokenLife" : 400
};
requestFormatters;
responseFormatters;
credentials = {};
token;
constructor(credentials, requestFormatters = {}, responseFormatters = {}, options = {}) {
this.credentials = credentials;
this.options = {...this.defaultOptions, ...options};
this.requestFormatters = requestFormatters;
this.responseFormatters = responseFormatters;
this.checkTokenRequestValidity = this.checkTokenRequestValidity.bind(this);
}
async makeRequest(requestDetails) {
let tokenValid = await this.validateToken();
if (!tokenValid) {
await this.refreshToken();
}
return this.actuallyMakeRequest(requestDetails);
}
validateToken() {
// make a request or whatever
// return a promise with the "yes/no"
}
refreshToken() {
// make your request with credentials
let tokenRequest = {
// your params here, with un/pw I guess?
};
return this.actuallyMakeRequest(tokenRequest)
.then(this.handleTokenResponse);
}
handleTokenResponse(response)
{
// chck validity
this.token = resonse.token;
return somethingCompletelyDiffernt;
}
actuallyMakeRequest(requestObject)
{
const formattedRequest = this.formatRequest(requestObject);
return makeHttpRequest(formattedRequest)
// NOTE: getRepsponseFormatter is an active call that builds a function
// to handle the response!
.then(this.getResponseFormatter(requestObject))
.catch((rejection) => { /** who cares */}); //etc etc etc
}
formatRequest(requestObject) {
const requestType = requestObject.type ?? "unknown";
if (typeof this.requestFormatters[requestType] === 'function') {
return this.requestFormatters[requestType](requestObject);
}
return this.requestFormatters.__defaultFormatter(requestObject);
}
// creates a formatting function and returns it for later use in the promise
// chain.
getResponseFormatter(requestObject)
{
const requestType = requestObject.type ?? "unknown";
if (typeof this.responseFormatters[requestType] === 'function') {
// binding the request object for later use in the formatter
return this.responseFormatters[requestType].bind(null, requestObject);
}
return ((response) => {
// format response
// you can check requestObject because it is in the containing scope
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment