0.0.1 • Published 8 years ago

ajax-infinity v0.0.1

Weekly downloads
5
License
MIT
Repository
github
Last release
8 years ago

NOTE This library is still under development, and is "pre-alpha"

ajax-infinity

Modern Promise-based AJAX Library

Basics

Make a simple GET request

import Request from 'ajax-infinity'

Request.create('/user/123/resume.md', 'GET')

GET /user/123/resume.md

Constructor

You can also use Request as a constructor

new Request('/user/123/resume.md', 'GET')

Request methods

The Request class has shortcut methods for creating the various HTTP request methods.

  • delete
  • index
  • get
  • head
  • options
  • post
  • put
  • trace

All Request methods accept the following parameters

Request.get(String uri[, Object settings])

Request.get('/user/123/resume.md')

GET /user/123/resume.md

URL Parameters

let params = {section: 'summary'}

Request.get('/user/123/resume.md', { params })

GET /user/123/resume.md?section=summary

URL Function Parameters

You can set a URL parameter to a Function, which will be evaluated when the Request URI is generated.

let params = {
  time: () => Date.now()
}

Request.get('/user/123/resume.md', { params })

GET /user/123/resume.md?time=1471364810350

Sending Data

To send data with a request, use the data Request setting

let data = textarea.value

Request.post('user/123/resume.json', {data})

Promises

Creating a Request will by default return a Promise for handling responses and request errors asynchronously, and will also send the request immediately.

Request.get('/user/123/resume.md').then((response) => {

  console.log('Success!', response.responseText);

}).catch((error) => {

  console.error('Failed.', error.message);
})

To prevent the request from sending immediately, and to receive a reference to the Request instead of a Promise, pass immediate: false when creating the Request. You can then call send() on the Request object when you are ready.

let req = Request.get('/user/123/resume.md', {immediate: false})

// do other needfuls

req.send().then((response) => console.log(response.responseText))

BaseURI

You can prepend a base URI to any request URI using the baseURI Request setting

Request.get('/user/123/resume.md', {baseURI: '/webservice/public'})

GET /webservice/public/user/123/resume.md

JSON

Responses where the HTTP response header content-type: application/json is present, or the request URI ends in .json will by default be parsed using JSON.parse(). Successful parsing will cause the request chain Promise to resolve with the resulting object. This operation is performed by the JSONParser middleware in its default configuration (See: Middleware section).

Request.get('/user/123/resume.json').then((resume) => {
  console.log(resume.summary)
})

You can prevent the JSONParser middleware from running by setting the the json request setting to false.

Request.get('/user/123/resume.json', {json: false}).then((response) => {
  console.log('Raw response', response.responseText)
  console.log('Headers', response.headers)
})

Timeouts

To prevent a request from hanging, you can set the timeout Request setting to a Number value in milliseconds. If the timeout is reached, an error will be thrown.

// ridiculously short timeout, 1ms
Request.get('/user/123/resume.md', {timeout: 1}).catch((error) => {
 if (error.timeout)
  console.error(`As expected, the request timed out in ${error.timeout.ms}ms`)
})

When the timeout Request setting is present, the Timeout middleware will be used (See: Middleware section)

Progress

Sometimes you want to monitor the progress of a download or upload via AJAX. To make this simple, ajax-infinity includes the progress Request setting, which should be a callback function, to be called on every AJAX update event.

let download = (percent, transferred, total) => console.log(`${percent}% downloaded`)

Request.get('/user/123/profile/photo-original.jpg', {progress: download})

let upload = (percent, transferred, total) => console.log(`uploaded ${transferred} of ${total} (${percent}% complete)`)

Request.post('/user/123/profile/photo.json', {progress: upload, data: photo})

When the progress Request setting is present, the Progress middleware will be used (See: Middleware section)

Profiles

Profiles allow you to configure default settings for AJAX requests. In the first example, we create a default profile, which will be used by all AJAX requests, unless specified otherwise. This profile simply prepends a base URI to all request URIs.

import Request from 'ajax-infinity'
import Profile from 'ajax-infinity'

new Profile('default', {
  baseURI: '/webservice/public'
})

Request.get('/user/123/resume.md')

With the default Profile in place, the Request URI will be as follows:

GET /webservice/public/user/123/resume.md

To ignore the default Profile, pass null as the profile setting value

Request.get('/user/123/resume.md', {profile: null})

GET /user/123/resume.md

Request settings can be added to a Profile, and will then be included in any AJAX request which utilizes that Profile

new Profile('admin', {
  baseURI: '/webservice/admin',
  username: 'admin@email.co',
  password: 'secret'
})

NOTE: The profile Request setting is ignored by Profile

Use a specific profile by name

Request.delete('/user/456.json', {profile: 'admin'})

DELETE admin%40email.co:secret@/webservice/admin/user/456.json

Overwrite a profile setting with a request setting

let username = 'bugs@email.co',
    password = 'onoes',
    bug = { messsage: 'feature is broken; fix it!' }

Request.post('/issues/bug.json', {profile: 'admin', username, password, data: bug})

POST bugs%40email.co:onoes@/webservice/admin/issues/bug.json

You can modify the settings of a Profile.

Profile.get('admin').set('password', 'newpassword')

You can completely overwrite a Profile by instantiating a new profile using the same name, and passing true as the third parameter, which will force the overwrite.

new Profile('admin', {
  baseURI: '/webservice/admin',
  username: 'admin@email.co',
  password: 'newpassword',
  clientTime: () => Date.now()
}, true);

If you prefer all Request creations to return a reference to the Request it self, and not a Promise, you can set immediate: false on the default Profile

new Profile('default', {
 immediate: false
})

let req = Request.get('/user/123/resume.md') // now returns Request object itself

req.send() // returns Promise

Request.get('/user/123/resume.md').then((response) => console.log(response)) // TypeError: then is not a function

Middleware

The ajax-infinity library includes the following middleware classes, which have been covered briefly above.

  • JSONParser (Middleware/JSONParser)
  • Timeout (Middleware/Timeout)
  • Progress (Middleware/Progress)

JSONParser

The JSONParser middleware detects JSON responses, and automatically attempts to parse the responseText using JSON.parse(). Here is the code which handles this operation

serverDidRespond(response, request) {
 if (/^application\/json/.test(response.headers['content-type']) || /\.json$/.test(request.uri)) {
  try {
   let json = JSON.parse(response.responseText);
	 response.responseValue = json; // if present, the Promise will resolve as response.responseValue
  } catch(error) {
   request.error(error); // send parse error to request, so .catch() can receive it
  }
 }
}

To explicitely use middleware with a request, the use Request setting is provided. The following requests are equivalent

import JSONParser from 'ajax-infinity/Middleware'

Request.get('/user/123/resume.json')

Request.get('/user/123/resume.json', {use: new JSONParser})

NOTE: The use setting can be a single instance of a Middleware object, or an array of Middleware objects. When use is an array, Request lifecycle methods will be called in the order they appear in the array.

Timeout

The Timeout middleware can cancel a long-running request after a given number of milliseconds. Here is the code which handles the timeout operation

	requestWillOpen(request) {

		let ms = this.settings(request)
		let timer = function (resolve, reject) {

			setTimeout(() => {

				// create custom error

				let error = new Error(`Request timed out (${ms}ms)`)
				error.timeout = {ms}

				// reject promise

				reject(error)

			}, ms)
		}

		// set timeout on XMLHttpRequest
		// to make sure request is cancelled

		request.native.timeout = ms

		//debugger

		request.promise(timer)
	}
}

The following requests are equivalent

import Timeout from 'ajax-infinity/Middleware'

Request.get('/user/123/profile/photo-original.jpg', {timeout: 1000})

Request.get('/user/123/profile/photo-original.jpg', {use: new Timeout(1000)})

Progress

The Progress middleware allows you to monitor the progress of requests via the native XMLHttpRequest progress event. Here is the code which handles this

requestWillSend(request) {
 this._callback = request.addEventListener('progress', (e) => {
  let {loaded, total = null} = e
  this.config.callback(total ? (loaded / total * 100) : null, loaded, total);
 })
}

The following requests are equivalent

import Progress from 'ajax-infinity/Middleware'

let download = (percent, transferred, total) => console.log(`${percent}` downloaded`)

Request.get('/user/123/profile/image-original.jpg', {progress: download})

Request.get('/user/123/profile/image-original.jpg', {use: new Progress(download)})

Custom Middleware

You can create your own middleware by extending the Middleware class. In this example, we'll create middleware that will detect and parse Markdown responses, and resolve the Request promise with the resulting HTML

import {default as markdown} from 'markdown' // https://github.com/evilstreak/markdown-js
import Middleware from 'ajax-infinity'

class MarkdownParser extends Middleware {

 // the key marks this middleware as exclusive
 // any middleware with the same key used before
 // this middleware will be ignored, and allows
 // you to override default middleware

 get key() { return 'markdown' }

 constructor(config = {}) {
  // set default dialect, if none provided
  let {dialect = 'Gruber'} = config
  super({dialect}) // pass config to super
 }

 serverDidRespond(response, request) {
  // if content type is "text/markdown" or URI ends in .md, try to parse it
  if (response.headers['content-type'] === 'text/markdown' || /\.md$/.test(request.uri)) {
   try {
    let html = markdown.toHTML(response.responseText)
    response.responseValue = html // resolve promise with html
   } catch(error) {
    request.error(error)
   }
  }
 }

}

Now let's use our new middleware class

Request.get('/user/123/resume.md', {use: new MarkdownParser}).then((html) => {

 // update page with resume HTML
 document.querySelector('.resume').innerHTML = html

}).catch((error) => {

 // display parse error
 document.querySelector('.resume').innerHTML = `<p class="error">${error.message}</p>`
})

Override Default Middleware

What if you want some Timeout middleware, but what's provided by ajax-infinity doesn't meet your needs? Let's say, for example, you want to be able to automatically retry a request on timeout n number of times, instead of throwing an error.

import Timeout from 'ajax-infinity/Middleware'

class TimeoutRetry extends Timeout {

 // constructor can accept ms and retry, defaulting to no retries, to emulate regular timeout

 constructor(ms, retry = 0) {
  super(ms)
  this.config.retry = retry
  this.tries = 0
 }

 requestWillOpen(request) {
  let {ms, retry} = this.settings(request);

  this.tries++

  let timer =(resolve, reject) => {

   setTimeout(resolve, ms)

  }).then(() => {

   if (this.tries === retry) {

    // retry limit reached, throw error

    let error = new Error(`Request timed out (${ms}ms)`)
    error.timeout = {ms}
    request.error(error)

   } else {

    // try request again

    request.send()
  })

 request.promise = () => Promise.all([timer, request.promise()])
 }
}

Now let's use the custom TimeoutRetry middleware with a request

Request.get('/user/123/resume.md', {use: new TimeoutRetry(500, 3)}).catch((error) => {
 console.error('Request timed out 3 times, with a 500ms timeout')
})

What if you want to use your TimeoutRetry instead of the default Timeout. The MiddlewareBinding class is provided for this task

import TimeoutRetry from 'my-project-middleware'
import MiddlewareBinding from 'ajax-infinity'

new Profile('default', {
 use: new MiddlewareBinding('timeout', TimeoutRetry, 'apply')
})

Now, whenever you use the timeout Request setting, TimeoutRetry will be used instead of the default Timeout middleware.

Request.get('/user/123/resume.md', {timeout: [1000, 3]})

The first parameter of the MiddlewareBinding constructor is the settings name to use. The second is the class to instantiate when the setting is present, and the third is how to pass the setting value to the middleware: "apply" will spread arrays when calling the middleware constructor, e.g. the settings {timeout: [1000, 3]} would result in {use: new TimeoutRetry(...settings.timeout)}. If a non-Array setting is passed to an "apply" binding, it will be called normally, e.g. the settings {timeout: 1000} would result in {use: new TimeoutRetry(settings.timeout)}.