@argab/request-service

3.3.0 • Public • Published

Request Service

Provides a fully customizable Library for handling API using Request Repositories and Stubs.

Initialisation

  1. Create your own ES-6 classes implementing "RequestClient" and "RequestHandler" Interfaces from the package.
//.......... ApiClient.js

import {RequestClient} from '@argab/request-service'
import axios from 'axios'

const _axios = (config) => axios.create(config)

export default class extends RequestClient {

    get({headers, uri, params}) {
        return _axios({headers}).get(uri, params)
    }

    post({headers, uri, params}) {
        return _axios({headers}).post(uri, params)
    }

    patch({headers, uri, params}) {
        return _axios({headers}).patch(uri, params)
    }

    put({headers, uri, params}) {
        return _axios({headers}).put(uri, params)
    }

    delete({headers, uri, params}) {
        return _axios({headers}).delete(uri, params)
    }

}

//........... ApiHandler.js

import {RequestHandler} from '@argab/request-service'

export default class extends RequestHandler {

    /*
    * response checking method
    * method executes within a Promise.prototype.then()
    * @param: {Object} incoming response data
    * @return: Boolean
    * */
    isSuccess(response) {
    }

    /*
    * response checking method
    * method executes within a Promise.prototype.then()
    * @param: {Object} incoming response data
    * @return: Boolean
    * */
    isError(response) {
    }

    /*
    * method executes within a Promise.prototype.then()
    * @param: {Object} incoming response data
    * @return: void
    * */
    onSuccess(response) {
    }

    /*
    * method executes within a Promise.prototype.then()
    * @param: {Object} incoming response data
    * @return: void
    * */
    onError(response) {
    }

    /*
    * method executes within a Promise.prototype.catch()
    * @param: {Object} error
    * @return: void
    * */
    onCatch(error) {
    }

    /*
    * method executes within a Promise.prototype.finally()
    * @param: {Object} request data
    * @return: void
    * */
    onFinally(data) {
    }

    /*
    * method executes before request sent
    * @param: {Object} request data
    * @return: void
    * */
    before(data) {
    }

    /*
    * method executes at the start of a Promise.prototype.then()
    * @param: {Object} response
    * @return: void
    * */
    after(response) {
    }

    /*
    * method executes at the start of a Promise.prototype.catch()
    * @param: {Object} error
    * @return: void
    * */
    afterCatch(error) {
    }

    /*
    * method executes at the start of a Promise.prototype.finally()
    * @param: {Object} request data
    * @return: void
    * */
    afterFinally(data) {
    }
}
  1. Initialise the Request Service and connect it to your App:
import {RequestService} from '@argab/request-service'
import ApiClient from './src/api/ApiClient'
import ApiHandler from './src/api/ApiHandler'

MyApp.prototype.request = new RequestService({
  config: {
      client: ApiClient,
      handler: ApiHandler
  }
})

new MyApp()

Simple request

import ApiHandler from './src/api/ApiHandler'
import SomeHandler from './src/api/SomeHandler'
import SomeClient from './src/api/SomeClient'

this.request
    // Configuring (optional):
    .config({ 
        handler: [SomeHandler, ApiHandler],
        client: SomeClient,
        headers: {'Content-Type': 'application/json;charset=UTF-8'},
        
        log: false  // (TRUE as a default) Use this flag to enable/disable 
                    // the request logging on the main Request instance;
                    // The requests log may be retrieved from the main instance 
                    // by calling "getLog" method (f.e: this.request.getLog())
    })
    
    // Mediators (optional):
    .unlog() // Use this flag to disable the request logging
    .headers({'Content-Type': 'application/json;charset=UTF-8'})
    .json() // Similar to .headers({'Content-Type': 'application/json;charset=UTF-8'})
    .encode() // Similar to .headers({'Content-Type': 'application/x-www-form-urlencoded'})
    .form() // Similar to .headers({'Content-Type': 'multipart/form-data'})
    .html() // Similar to .headers({'Accept': 'text/html'})
    .stream() // Similar to .headers({'Content-Type': 'application/octet-stream'})
    
    // Client Decorator (required):
    .post('http://some.url', {parameters})
    
    // Request Decorators (optional):
    .success(response => {}) // or .then(...)
    .error(response => {}) // If not called, then ApiHandler`s "onError" will be executed.
    .catch(error => {}) // If not called, then ApiHandler`s "onCatch" will be executed.
    .finally(requestData => {}) // If not called, then ApiHandler`s "onFinally" will be executed.

Request Repositories And Stubs

Request repositories allow you to move the complex logic of creating requests to a separate namespace (directory)

//............ MyApp.js

import {RequestService} from '@argab/request-service'
import ApiClient from './src/api/_clients/ApiClient'
import ApiHandler from './src/api/_handlers/ApiHandler'

MyApp.prototype.request = new RequestService({
  getRepo: (path) => {
      const Repo = require(`./api/${path}`).default
      return new Repo
  },
  getStub: (path) => {
      const Stub = require(`./api/_stubs/${path}`).default
      return new Stub
  },
  useStubs: true, 
  config: {
      client: ApiClient,
      handler: ApiHandler
  }
})

new MyApp()

//........... /api/orders.js

import {RequestRepository} from '@argab/request-service'

export default class extends RequestRepository {
    getOrders(params) {
        return this.client.get('http://some.url/orders', params)
    }
}

//........... /api/_stubs/orders.js

import {RequestRepository} from '@argab/request-service'

export default class extends RequestRepository {
    getOrders(params) {
        return this.client.stubData({SOME_STUB_DATA}).get('/orders', params)
    }
}

//.......... getOrders.js

// if "useStubs" set to true, then stubs reposiory will be connected first (only if exists).

this.request.repo('orders').getOrders({params}).then(...).error(...)

// Anyway, calling .stub() obviously will connect the stubs reposiory even if "useStubs" set to false.

this.request.stub('orders').getOrders({params}).then(...).error(...)

Use of Loaders

The "Loader" meaning is a Tool that indicates a live cycle of the request. For example a Loading Spinner in the HTML Document.

//............ MyLoader.js

import {RequestLoader} from '@argab/request-service'

class MyLoader extends RequestLoader {

    _data
    
    /*
    * @property: Displays a number of requests 
    * that uses Loader and having pending status
    * */
    pending

    constructor(data) {
        super()
        this._data = data
    }

    start() {
        console.log('Requests in pending status: ', this.pending)
        console.log('Request pending: ', `${this._data.method.toUpperCase()} ${this._data.uri}...`)
    }

    end() {
        console.log('Request complete: ', `${this._data.method.toUpperCase()} ${this._data.uri}.`)
    }

}


//............ MyApp.js

import {RequestService} from '@argab/request-service'
import ApiClient from './src/api/_clients/ApiClient'
import MyLoader from './src/api/_loaders/MyLoader'
import MyCustomLoader from './src/api/_loaders/MyCustomLoader'

MyApp.prototype.request = new RequestService({
  config: {
      client: ApiClient,
      loader: MyLoader,
      useLoader: true // Loaders switcher
  }
})

const app = new MyApp()

app.request
    // Loader switchers (optional):
    .config({loader: MyCustomLoader}) // Setting any custom Loader for the request
    .useLoader() // Boolean true as a default
    .bg() // Similar to .useLoader(false)
    
    .get('http://some.url').success(...).error(...).catch(...).finally(...)

Extending Request And RequestMiddleware

By Passing argument named "extend" allows to inject any custom pre-request and post-request handling methods to the Base Request's prototype.

//............ MyApp.js

import {RequestService} from '@argab/request-service'
import ApiClient from './src/api/_clients/ApiClient'
import ApiHandler from './src/api/_handlers/ApiHandler'

MyApp.prototype.request = new RequestService({
  config: {
      client: ApiClient,
      handler: ApiHandler
  },
  extend: {
        middleware: {
            // Be aware to declare via "function" as it allows to access to the current context by "this".
            awesome: function () {
                console.log('This is my awesome middleware function!')
                
                this.config({headers: {'X-AwesomeHeader': 'v1.0.0'}})
            }
        },
        request: {
            done: function (messageOnSuccess) {
                console.log(`This is what my ApiHandler would notify about at response success: `, messageOnSuccess)
    
                this.data.done = messageOnSuccess
            },
            alert: function (messageOnError) {
                console.log(`This is what my ApiHandler would notify about at response error: `, messageOnError)
    
                this.data.alert = messageOnError
            }
        }
  }
})

//........... ApiHandler.js

import {RequestHandler} from '@argab/request-service'

export default class extends RequestHandler {

    _data
    
    constructor(data) {
        super()
        this._data = data
    }

    isSuccess(response) {
        return true
    }

    after(response) {
        const notifySuccess = this._data.done;
        const notifyError = this._data.alert;

        if (this.isSuccess(response) && notifySuccess) {
            console.log(notifySuccess)
        } else if (this.isError(response) && notifyError) {
            console.error(notifyError)
        }
    }
}

//////////////////////////

const app = new MyApp()

app.request.awesome().get('/posts').done('Wow, that`s awesome!').alert('Ooops...')

Output from tests:

This is my awesome middleware function!
Requests in pending status:  1
Request pending:  GET /posts...
This is what my ApiHandler would notify about at response success:  Wow, that`s awesome!
This is what my ApiHandler would notify about at response error:  Ooops...
Wow, that`s awesome!
Request complete:  GET /posts.

Retrying the Request

Request retrying available both from the Request Handler and Request call:

/////////// Retrying from Handler:

import {RequestHandler} from '@argab/request-service'

export default class extends RequestHandler {

    onFinally() {
        
        /*
        * (This method is Abstract (Not subject to redeclare))
        * The request restarting method.
        * @param: {Boolean}|{Function}(data):<Boolean|Promise> 
        *     - Boolean or a Function returning both Boolean or a Promise
        *       returning Boolean that whenever 
        *       is TRUE then restarts the request.
        * @return: void
        * */
        this.retry((data) => new Promise(resolve => {
            //...do some logic and restart the request:
            resolve(true)
        }))
        
        // ANOTHER WAYS:
        
        // getting restart at once:
        this.retry(true)
        
        // getting restart with some modifications:
        this.retry(data => {
            // changing the original request (optional):
            data.retryChain = ({chain}) => {
                chain.find(c => c.method === 'error').args[0] = () => {
                    console.log('"error" method has been overriden.')
                }
                return chain
            }
            //...do some logic and restart the request:
            return true
        })
    }
}

/////////// Retrying from the request call:

const result = await app.request
    .awesome()
    .get('/posts')
    .error(() => { throw 'Oops!' })
    .retryMaxCount(3)
    .retryTimeout(3000)
    // .retry(true)
    .retryChain(({chain}) => {
        chain.find(c => c.method === 'error').args[0] = () => {
            console.log('"error" method has been overriden.')
        }
        return chain
    })
    .retryOnCatch(data => {
        return new Promise(res => {
            setTimeout(() => {
                console.error('Awesome error: ', data.dataError)
                res(true)
            }, 3000)
        })
    })

console.log(result)

Retry Methods:

/*
* This method doesn't restarts the request, 
* it brings the Function that
* takes the request data as an argument 
* and initiates the request restarting method.
* The Function executes at the end of a Promise.prototype.finally()
* @param: {Boolean}|{Function}(data):<Boolean|Promise> 
*     - Boolean or a Function returning both Boolean or a Promise
*       returning Boolean that whenever is TRUE then restarts the request
* @return: void
* */
retry (resolve) {}

/*
* Restarts the request on request errors.
* The Function executes at the end of a Promise.prototype.finally()
* @param: {Boolean}|{Function}(data):<Boolean|Promise> 
*     - Boolean or a Function returning both Boolean or a Promise
*       returning Boolean that whenever is TRUE then restarts the request
* @return: void
* */
retryOnCatch (resolve) {}

/*
* Overrides the current request`s methods call chain on retry.
* The Function executes within a retry method.
* @example: ....retryChain(({set}) => set.json()
*   .post('http://some.url', {params})
*   .success(...).error(...).catch(...))
* @param: {Function}({set, chain, data}):<void|Array>
*     - @property "set" creates a new set
*       of the current request`s methods call chain.
*     - @property "chain" provides an Array
*       [{method, args}, {method, args}, ....]
*       containing the current request`s methods call chain.
*     - @property "data" - current request`s data.
* @return: void
* */
retryChain (callback) {}

/*
* Sets a max number of retry attempts
* @param: {Number}
* @return: void
* */
retryMaxCount (count) {}

/*
* Sets the timeout in miliseconds between retry attempts
* @param: {Number}
* @return: void
* */
retryTimeout (miliseconds) {}

Package Sidebar

Install

npm i @argab/request-service

Weekly Downloads

37

Version

3.3.0

License

MIT

Unpacked Size

129 kB

Total Files

26

Last publish

Collaborators

  • argab