ajax-infinity
v0.0.1
Published
Modern Promise-based AJAX library
Downloads
4
Readme
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: '[email protected]',
password: 'secret'
})
NOTE: The
profile
Request setting is ignored byProfile
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 = '[email protected]',
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: '[email protected]',
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. Whenuse
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)}
.