Last active
May 20, 2022 18:35
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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}); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const requestFormatterExternal = { | |
getUsers : function(request) { | |
}, | |
getLogs : function(request) { | |
}, | |
__defaultFormatter: function(request) { | |
} | |
}; | |
const responseFormatterExternal = { | |
getUsers : function(request, response) { | |
}, | |
getLogs : function(request, response) { | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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