nodulator
Version:
Complete NodeJS Framework for Restfull APIs
146 lines (123 loc) • 4.26 kB
JavaScript
/*
* Copyright 2012-2014 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/
(function (define, global, document) {
'use strict';
var undef;
define(function (require) {
var when, UrlBuilder, responsePromise, client;
when = require('when');
UrlBuilder = require('../UrlBuilder');
responsePromise = require('../util/responsePromise');
client = require('../client');
// consider abstracting this into a util module
function clearProperty(scope, propertyName) {
try {
delete scope[propertyName];
}
catch (e) {
// IE doesn't like to delete properties on the window object
if (propertyName in scope) {
scope[propertyName] = undef;
}
}
}
function cleanupScriptNode(response) {
try {
if (response.raw && response.raw.parentNode) {
response.raw.parentNode.removeChild(response.raw);
}
} catch (e) {
// ignore
}
}
function registerCallback(prefix, resolve, response, name) {
if (!name) {
do {
name = prefix + Math.floor(new Date().getTime() * Math.random());
}
while (name in global);
}
global[name] = function jsonpCallback(data) {
response.entity = data;
clearProperty(global, name);
cleanupScriptNode(response);
if (!response.request.canceled) {
resolve(response);
}
};
return name;
}
/**
* Executes the request as JSONP.
*
* @param {string} request.path the URL to load
* @param {Object} [request.params] parameters to bind to the path
* @param {string} [request.callback.param='callback'] the parameter name for
* which the callback function name is the value
* @param {string} [request.callback.prefix='jsonp'] prefix for the callback
* function, as the callback is attached to the window object, a unique,
* unobtrusive prefix is desired
* @param {string} [request.callback.name=<generated>] pins the name of the
* callback function, useful for cases where the server doesn't allow
* custom callback names. Generally not recommended.
*
* @returns {Promise<Response>}
*/
return client(function jsonp(request) {
return responsePromise.promise(function (resolve, reject) {
var callbackName, callbackParams, script, firstScript, response;
request = typeof request === 'string' ? { path: request } : request || {};
response = { request: request };
if (request.canceled) {
response.error = 'precanceled';
reject(response);
return;
}
request.callback = request.callback || {};
callbackName = registerCallback(request.callback.prefix || 'jsonp', resolve, response, request.callback.name);
callbackParams = {};
callbackParams[request.callback.param || 'callback'] = callbackName;
request.canceled = false;
request.cancel = function cancel() {
request.canceled = true;
cleanupScriptNode(response);
reject(response);
};
script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = response.url = new UrlBuilder(request.path, request.params).build(callbackParams);
function handlePossibleError() {
if (typeof global[callbackName] === 'function') {
response.error = 'loaderror';
clearProperty(global, callbackName);
cleanupScriptNode(response);
reject(response);
}
}
script.onerror = function () {
handlePossibleError();
};
script.onload = script.onreadystatechange = function (e) {
// script tag load callbacks are completely non-standard
// handle case where onreadystatechange is fired for an error instead of onerror
if ((e && (e.type === 'load' || e.type === 'error')) || script.readyState === 'loaded') {
handlePossibleError();
}
};
response.raw = script;
firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
});
});
});
}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); },
typeof window !== 'undefined' ? window : void 0,
typeof document !== 'undefined' ? document : void 0
// Boilerplate for AMD and Node
));