Move Moderated Grading to Redux

This is an initial stab at the conversion.  It gets most of the
boilerplate stuff in and working.

It also handles converting over the publish button.

closes CNVS-23313
closes CNVS-23037
closes CNVS-22746

Test Plan:
  - Set up your environment to function with moderated grading stuff
  - Go to a moderated grading assingment as a teacher
  - You should see the list of students that can be moderated
  - Test Plan from CNVS-22746:
    - Activate Moderated Grading feature flag
    - Create an assignment that is moderated
    - Submit a few assignments as students
    - Go to speedgrader on those assignments and give
      an initial grade
    - Go to the assignment page as the teacher and
      click the "Moderate" button
    - Click the "Publish" button
    - A confirmation box should appear
    - Cancelling should do nothing
    - Confirming should then flash a green success flash message
    - The grades should now be published to the gradebook.
  - The publish button should be disabled after doing the previous steps.
  - Refreshing the page should still have the button disabled.

Change-Id: I15f4bc5d71f7bd3120c4c2ba2d4d239e8ad22db7
Reviewed-on: https://gerrit.instructure.com/63547
Tested-by: Jenkins
Reviewed-by: Jon Willesen <jonw@instructure.com>
QA-Review: Jahnavi Yetukuri <jyetukuri@instructure.com>
Product-Review: Clay Diffrient <cdiffrient@instructure.com>
This commit is contained in:
Clay Diffrient 2015-09-17 15:24:38 -06:00
parent 073084b505
commit 25edf74d42
62 changed files with 6044 additions and 210 deletions

View File

@ -2,10 +2,21 @@ require [
'jquery' 'jquery'
'react' 'react'
'jsx/assignments/ModerationApp' 'jsx/assignments/ModerationApp'
], ($, React, ModerationApp) -> 'jsx/assignments/store/configureStore'
], ($, React, ModerationApp, configureStore) ->
React.render(ModerationApp({ store = configureStore({
student_submissions_url: ENV.URLS.student_submissions_url moderationStage: [],
publish_grades_url: ENV.URLS.publish_grades_url students: [],
urls: window.ENV.URLS
flashMessage: {
time: Date.now(),
message: '',
error: false
},
assignment: {
published: window.ENV.GRADES_PUBLISHED
}
}) })
, $('#assignment_moderation')[0])
React.render(ModerationApp(store: store), $('#assignment_moderation')[0])

View File

@ -184,8 +184,10 @@ class AssignmentsController < ApplicationController
js_env({ js_env({
:URLS => { :URLS => {
:student_submissions_url => polymorphic_url([:api_v1, @context, @assignment, :submissions]) + "?include[]=user_summary&include[]=provisional_grades", :student_submissions_url => polymorphic_url([:api_v1, @context, @assignment, :submissions]) + "?include[]=user_summary&include[]=provisional_grades",
:publish_grades_url => api_v1_publish_provisional_grades_url({course_id: @context.id, assignment_id: @assignment.id}) :publish_grades_url => api_v1_publish_provisional_grades_url({course_id: @context.id, assignment_id: @assignment.id}),
:list_gradeable_students => api_v1_course_assignment_gradeable_students_url({course_id: @context.id, assignment_id: @assignment.id}) + "?include[]=provisional_grades&per_page=50"
}}) }})
js_env(:GRADES_PUBLISHED => @assignment.grades_published?)
respond_to do |format| respond_to do |format|
format.html { render } format.html { render }

View File

@ -0,0 +1,43 @@
/** @jsx React.DOM */
define([
'jquery',
'react',
'i18n!moderated_grading',
'compiled/jquery.rails_flash_notifications'
], function ($, React, I18n) {
var FlashMessageHolder = React.createClass({
displayName: 'FlashMessageHolder',
getInitialState () {
return this.props.store.getState().flashMessage;
},
componentDidMount () {
this.props.store.subscribe(this.handleChange);
},
handleChange () {
this.setState(this.props.store.getState().flashMessage);
},
shouldComponentUpdate (nextProps, nextState) {
return nextState.time > this.state.time;
},
componentWillUpdate (nextProps, nextState) {
if (nextState.error) {
$.flashError(nextState.message);
} else {
$.flashMessage(nextState.message);
}
},
render () {
return null;
}
});
return FlashMessageHolder;
});

View File

@ -12,11 +12,23 @@ define([
actions: React.PropTypes.object.isRequired actions: React.PropTypes.object.isRequired
}, },
getInitialState () {
return this.props.store.getState().assignment;
},
componentDidMount () {
this.props.store.subscribe(this.handleChange);
},
handleChange () {
this.setState(this.props.store.getState().assignment);
},
handlePublishClick () { handlePublishClick () {
// Make a better looking confirm one day // Make a better looking confirm one day
var confirmMessage = I18n.t('Are you sure you want to do this? It cannot be undone and will override existing grades in the gradebook.') var confirmMessage = I18n.t('Are you sure you want to do this? It cannot be undone and will override existing grades in the gradebook.')
if (window.confirm(confirmMessage)) { if (window.confirm(confirmMessage)) {
this.props.actions.publishGrades(); this.props.store.dispatch(this.props.actions.publishGrades());
} }
}, },
@ -32,6 +44,7 @@ define([
type='button' type='button'
className='ModeratedGrading__Header-PublishBtn Button Button--primary' className='ModeratedGrading__Header-PublishBtn Button Button--primary'
onClick={this.handlePublishClick} onClick={this.handlePublishClick}
disabled={this.state.published}
> >
{I18n.t('Publish')} {I18n.t('Publish')}
</button> </button>

View File

@ -9,31 +9,21 @@ define([
var MARK_THREE = 2; var MARK_THREE = 2;
return React.createClass({ return React.createClass({
getInitialState () {
return ( propTypes: {
{submissions: []} students: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
);
},
componentDidMount () {
this.props.store.addChangeListener(this.handleStoreChange);
},
handleStoreChange () {
this.setState({submissions: this.props.store.submissions});
},
displayName: 'ModeratedStudentList',
selectCheckbox (submission, event) {
// this.actions.updateSubmission(submission);
}, },
renderSubmissionMark (submission, mark_number) { renderSubmissionMark (submission, mark_number) {
if(submission.provisional_grades[mark_number]){ if (submission.provisional_grades && submission.provisional_grades[mark_number]) {
return( return (
<div className='AssignmentList__Mark'> <div className='AssignmentList__Mark'>
<input type='radio' name={"mark_" + submission.id} /> <input type='radio' name={`mark_${submission.id}`} />
<span>{submission.provisional_grades[mark_number].score}</span> <span>{submission.provisional_grades[mark_number].score}</span>
</div> </div>
); );
}else{ } else {
return( return (
<div className='AssignmentList__Mark'> <div className='AssignmentList__Mark'>
<span>Speed Grader</span> <span>Speed Grader</span>
</div> </div>
@ -41,14 +31,14 @@ define([
} }
}, },
renderFinalGrade (submission) { renderFinalGrade (submission) {
if (submission.grade){ if (submission.grade) {
return( return (
<span className='AssignmentList_Grade'> <span className='AssignmentList_Grade'>
{submission.score} {submission.score}
</span> </span>
); );
}else{ } else {
return( return (
<span className='AssignmentList_Grade'> <span className='AssignmentList_Grade'>
- -
</span> </span>
@ -56,16 +46,16 @@ define([
} }
}, },
render () { render () {
return( return (
<ul className='AssignmentList'> <ul className='AssignmentList'>
{ {
this.state.submissions.map(function(submission) { this.props.students.map((submission) => {
return( return (
<li className='AssignmentList__Item'> <li className='AssignmentList__Item'>
<div className='AssignmentList__StudentInfo'> <div className='AssignmentList__StudentInfo'>
<input checked={submission.isSelected} type="checkbox" onChange={this.selectCheckbox.bind(null, submission)} /> <input checked={submission.isSelected} type='checkbox' />
<img className='img-circle AssignmentList_StudentPhoto' src={submission.user.avatar_image_url} /> <img className='img-circle AssignmentList_StudentPhoto' src={submission.avatar_image_url} />
<span>{submission.user.display_name}</span> <span>{submission.display_name}</span>
</div> </div>
{this.renderSubmissionMark(submission, MARK_ONE)} {this.renderSubmissionMark(submission, MARK_ONE)}
{this.renderSubmissionMark(submission, MARK_TWO)} {this.renderSubmissionMark(submission, MARK_TWO)}
@ -73,7 +63,7 @@ define([
{this.renderFinalGrade(submission)} {this.renderFinalGrade(submission)}
</li> </li>
); );
}.bind(this)) })
} }
</ul> </ul>
); );

View File

@ -5,36 +5,39 @@ define([
'i18n!moderated_grading', 'i18n!moderated_grading',
'./ModeratedStudentList', './ModeratedStudentList',
'./Header', './Header',
'./stores/ModerationStore', './FlashMessageHolder',
'./actions/ModerationActions' './actions/ModerationActions'
], function (React, I18n, ModeratedStudentList, Header, Store, Actions) { ], function (React, I18n, ModeratedStudentList, Header, FlashMessageHolder, Actions) {
return React.createClass({ return React.createClass({
displayName: 'ModerationApp', displayName: 'ModerationApp',
propTypes: { propTypes: {
student_submissions_url: React.PropTypes.string.isRequired, store: React.PropTypes.object.isRequired
publish_grades_url: React.PropTypes.string.isRequired },
getInitialState () {
return this.props.store.getState();
}, },
componentDidMount () { componentDidMount () {
this.actions.loadInitialSubmissions(this.props.student_submissions_url); this.props.store.subscribe(this.handleChange);
this.props.store.dispatch(Actions.apiGetStudents());
}, },
componentWillMount () { handleChange () {
this.store = new Store(); this.setState(this.props.store.getState());
this.actions = new Actions(this.store, {
publish_grades_url: this.props.publish_grades_url
});
}, },
render () { render () {
return ( return (
<div className='ModerationApp'> <div className='ModerationApp'>
<FlashMessageHolder store={this.props.store} />
<h1 className='screenreader-only'>{I18n.t('Moderate %{assignment_name}', {assignment_name: 'TODO!!!!!!!!'})}</h1> <h1 className='screenreader-only'>{I18n.t('Moderate %{assignment_name}', {assignment_name: 'TODO!!!!!!!!'})}</h1>
<Header actions={this.actions} /> <Header store={this.props.store} actions={Actions} />
<ModeratedStudentList actions={this.actions} store={this.store} /> <ModeratedStudentList {...this.state} />
</div> </div>
); );
} }
}); });

View File

@ -1,51 +1,90 @@
/** @jsx React.DOM */ /** @jsx React.DOM */
define([ define([
'jquery',
'i18n!moderated_grading',
'axios', 'axios',
'compiled/jquery.rails_flash_notifications' 'i18n!moderated_grading'
], function ($, I18n, axios) { ], function (axios, I18n) {
class ModerationActions {
constructor (store, opts) {
this.store = store;
if (opts && opts.publish_grades_url) {
this.publish_grades_url = opts.publish_grades_url;
}
}
updateSubmission (submission) { var ModerationActions = {
// Update the submission and then update the store
}
loadInitialSubmissions (submissions_url) { // Define 'constants' for types
axios.get(submissions_url) SELECT_STUDENT: 'SELECT_STUDENT',
.then(function (response) { UNSELECT_STUDENT: 'UNSELECT_STUDENT',
this.store.addSubmissions(response.data); SELECT_ALL_STUDENTS: 'SELECT_ALL_STUDENTS',
}.bind(this)) UNSELECT_ALL_STUDENTS: 'UNSELECT_ALL_STUDENTS',
.catch(function (response) { SELECT_MARK: 'SELECT_MARK',
console.log('finished'); UPDATE_MODERATION_SET: 'UPDATE_MODERATION_SET',
}); PUBLISHED_GRADES: 'PUBLISHED_GRADES',
} PUBLISHED_GRADES_FAILED: 'PUBLISHED_GRADES_FAILED',
GOT_STUDENTS: 'GOT_STUDENTS',
publishGrades () { selectStudent (studentId) {
var axiosPostOptions = { return {
xsrfCookieName: '_csrf_token', type: this.SELECT_STUDENT,
xsrfHeaderName: 'X-CSRF-Token' payload: { studentId }
}; };
axios.post(this.publish_grades_url, {}, axiosPostOptions) },
gotStudents (students) {
return {
type: this.GOT_STUDENTS,
payload: { students }
};
},
publishedGrades (message) {
return {
type: this.PUBLISHED_GRADES,
payload: {
message,
time: Date.now()
}
};
},
publishGradesFailed (message) {
return {
type: this.PUBLISHED_GRADES_FAILED,
payload: {
message,
time: Date.now()
},
error: true
};
},
publishGrades (ajaxLib) {
return (dispatch, getState) => {
var endpoint = getState().urls.publish_grades_url;
ajaxLib = ajaxLib || axios;
ajaxLib.post(endpoint)
.then((response) => { .then((response) => {
$.flashMessage(I18n.t('Success! Grades were published to the grade book.')); dispatch(this.publishedGrades(I18n.t('Success! Grades were published to the grade book.')));
}) })
.catch((response) => { .catch((response) => {
if ((response.status === 400) && (response.data.message)){ if (response.status === 400) {
$.flashError(response.data.message); dispatch(this.publishGradesFailed(I18n.t('Assignment grades have already been published.')));
} else { } else {
$.flashError(I18n.t('Error! A problem happened publishing grades.')); dispatch(this.publishGradesFailed(I18n.t('An error occurred publishing grades.')));
} }
}); });
};
},
apiGetStudents (ajaxLib) {
return (dispatch, getState) => {
var endpoint = getState().urls.list_gradeable_students;
ajaxLib = ajaxLib || axios;
ajaxLib.get(endpoint)
.then((response) => {
dispatch(this.gotStudents(response.data));
})
.catch((response) => {
throw new Error(response);
});
};
} }
} };
return ModerationActions; return ModerationActions;
}); });

View File

@ -0,0 +1,84 @@
/** @jsx React.DOM */
define([
'underscore',
'redux',
'../actions/ModerationActions'
], function (_, Redux, ModerationActions) {
var { combineReducers } = Redux;
var studentHandlers = {};
studentHandlers[ModerationActions.GOT_STUDENTS] = (state, action) => {
return state.concat(action.payload.students);
};
var flashHandlers = {};
flashHandlers[ModerationActions.PUBLISHED_GRADES] = (state, action) => {
// Don't mutate the existing state.
var newState = _.extend({}, state);
newState.time = action.payload.time;
newState.message = action.payload.message;
newState.error = false;
return newState;
};
flashHandlers[ModerationActions.PUBLISHED_GRADES_FAILED] = (state, action) => {
// Don't mutate the existing state.
var newState = _.extend({}, state);
newState.time = action.payload.time;
newState.message = action.payload.message;
newState.error = true;
return newState;
};
function urls (state, action) {
return state || {};
}
function students (state, action) {
state = state || [];
var handler = studentHandlers[action.type];
if (handler) return handler(state, action);
return state;
}
function addUserToModeration (state, action) {
// Don't mutate the existing state.
var newState = _.extend({}, state);
var { type, payload } = action;
if (type === ModerationActions.SELECT_STUDENT) {
newState.moderationStage.push(payload.studentId);
return newState;
}
return {moderationStage: []};
}
function flashMessage (state, action) {
state = state || {};
var handler = flashHandlers[action.type];
if (handler) return handler(state, action);
return state;
}
function assignment (state, action) {
state = state || {};
if (action.type === ModerationActions.PUBLISHED_GRADES) {
// Don't mutate the existing state.
var newState = _.extend({}, state);
newState.published = true;
return newState;
}
return state;
}
return combineReducers({
students,
urls,
flashMessage,
assignment
});
});

View File

@ -0,0 +1,18 @@
/** @jsx React.DOM */
define([
'redux',
'redux-thunk',
'../reducers/rootReducer'
], function (Redux, ReduxThunk, rootReducer) {
var { createStore, applyMiddleware } = Redux;
var createStoreWithMiddleware = applyMiddleware(
ReduxThunk
)(createStore);
return function configureStore (initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
};
});

View File

@ -1,48 +0,0 @@
/** @jsx React.DOM */
define(['underscore', 'Backbone'], function(_){
class ModerationStore {
constructor () {
this.events = _.extend({}, Backbone.Events);
this.submissions = [];
}
/**
* Adds a handler to be fired when the change event occurs.
* @param {Function} handler Function to be called when the event is fired
*/
addChangeListener (handler) {
this.events.on('change', handler);
}
/**
* Removes a handler from the store.
* @param {Function} handler Function to be called when the event is fired
*/
removeChangeListener (handler) {
this.events.off('change', handler);
}
/**
* Add submissions to the store
*
* @param {Array} submission An array of submission objects
*/
addSubmissions (submissions) {
this.submissions = this._mergeArraysById(this.submissions, submissions);
this.events.trigger('change');
}
_mergeArraysById (arrayOne, arrayTwo) {
return _.map(arrayTwo, (item) => {
var foundItem = _.find(arrayOne, (arrayOneItem) => {
return arrayOneItem.id === item.id;
});
return _.extend(item, foundItem);
});
}
}
return ModerationStore;
});

View File

@ -26,9 +26,9 @@
"react-select-box": "https://github.com/instructure-react/react-select-box.git#b1ddd39223d48793fbe3dc4e87aca00d57197b5f", "react-select-box": "https://github.com/instructure-react/react-select-box.git#b1ddd39223d48793fbe3dc4e87aca00d57197b5f",
"react-tray": "~0.1.2", "react-tray": "~0.1.2",
"classnames": "~2.1.2", "classnames": "~2.1.2",
"moment": "~2.10.3",
"reflux": "~0.2.7",
"moment": "~2.10.6", "moment": "~2.10.6",
"axios": "~0.5.4" "reflux": "~0.2.7",
"axios": "~0.5.4",
"when": "~3.7.3"
} }
} }

View File

@ -0,0 +1,46 @@
{
"name": "when",
"version": "3.7.3",
"main": "when.js",
"moduleType": [
"amd",
"node"
],
"description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.",
"keywords": [
"Promises/A+",
"promises-aplus",
"promise",
"promises",
"deferred",
"deferreds",
"when",
"async",
"asynchronous",
"cujo"
],
"homepage": "https://github.com/cujojs/when",
"authors": [
"Brian Cavalier <brian@hovercraftstudios.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"**/*.md",
"docs",
"benchmark",
"node_modules",
"bower_components",
"test",
"build"
],
"_release": "3.7.3",
"_resolution": {
"type": "version",
"tag": "3.7.3",
"commit": "d748316b0a1ef9f323edd5c9eac899f221c7fa13"
},
"_source": "git://github.com/cujojs/when.git",
"_target": "~3.7.3",
"_originalSource": "when"
}

View File

@ -0,0 +1,24 @@
Open Source Initiative OSI - The MIT License
http://www.opensource.org/licenses/mit-license.php
Copyright (c) 2011 Brian Cavalier
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,23 @@
{
"name": "when",
"version": "3.7.3",
"main": "when.js",
"moduleType": ["amd", "node"],
"description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.",
"keywords": ["Promises/A+", "promises-aplus", "promise", "promises", "deferred", "deferreds", "when", "async", "asynchronous", "cujo"],
"homepage": "https://github.com/cujojs/when",
"authors": [
"Brian Cavalier <brian@hovercraftstudios.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"**/*.md",
"docs",
"benchmark",
"node_modules",
"bower_components",
"test",
"build"
]
}

View File

@ -0,0 +1,262 @@
/** @license MIT License (c) copyright 2013-2014 original author or authors */
/**
* Collection of helper functions for interacting with 'traditional',
* callback-taking functions using a promise interface.
*
* @author Renato Zannon
* @contributor Brian Cavalier
*/
(function(define) {
define(function(require) {
var when = require('./when');
var Promise = when.Promise;
var _liftAll = require('./lib/liftAll');
var slice = Array.prototype.slice;
var makeApply = require('./lib/apply');
var _apply = makeApply(Promise, dispatch);
return {
lift: lift,
liftAll: liftAll,
apply: apply,
call: call,
promisify: promisify
};
/**
* Takes a `traditional` callback-taking function and returns a promise for its
* result, accepting an optional array of arguments (that might be values or
* promises). It assumes that the function takes its callback and errback as
* the last two arguments. The resolution of the promise depends on whether the
* function will call its callback or its errback.
*
* @example
* var domIsLoaded = callbacks.apply($);
* domIsLoaded.then(function() {
* doMyDomStuff();
* });
*
* @example
* function existingAjaxyFunction(url, callback, errback) {
* // Complex logic you'd rather not change
* }
*
* var promise = callbacks.apply(existingAjaxyFunction, ["/movies.json"]);
*
* promise.then(function(movies) {
* // Work with movies
* }, function(reason) {
* // Handle error
* });
*
* @param {function} asyncFunction function to be called
* @param {Array} [extraAsyncArgs] array of arguments to asyncFunction
* @returns {Promise} promise for the callback value of asyncFunction
*/
function apply(asyncFunction, extraAsyncArgs) {
return _apply(asyncFunction, this, extraAsyncArgs || []);
}
/**
* Apply helper that allows specifying thisArg
* @private
*/
function dispatch(f, thisArg, args, h) {
args.push(alwaysUnary(h.resolve, h), alwaysUnary(h.reject, h));
tryCatchResolve(f, thisArg, args, h);
}
function tryCatchResolve(f, thisArg, args, resolver) {
try {
f.apply(thisArg, args);
} catch(e) {
resolver.reject(e);
}
}
/**
* Works as `callbacks.apply` does, with the difference that the arguments to
* the function are passed individually, instead of as an array.
*
* @example
* function sumInFiveSeconds(a, b, callback) {
* setTimeout(function() {
* callback(a + b);
* }, 5000);
* }
*
* var sumPromise = callbacks.call(sumInFiveSeconds, 5, 10);
*
* // Logs '15' 5 seconds later
* sumPromise.then(console.log);
*
* @param {function} asyncFunction function to be called
* @param {...*} args arguments that will be forwarded to the function
* @returns {Promise} promise for the callback value of asyncFunction
*/
function call(asyncFunction/*, arg1, arg2...*/) {
return _apply(asyncFunction, this, slice.call(arguments, 1));
}
/**
* Takes a 'traditional' callback/errback-taking function and returns a function
* that returns a promise instead. The resolution/rejection of the promise
* depends on whether the original function will call its callback or its
* errback.
*
* If additional arguments are passed to the `lift` call, they will be prepended
* on the calls to the original function, much like `Function.prototype.bind`.
*
* The resulting function is also "promise-aware", in the sense that, if given
* promises as arguments, it will wait for their resolution before executing.
*
* @example
* function traditionalAjax(method, url, callback, errback) {
* var xhr = new XMLHttpRequest();
* xhr.open(method, url);
*
* xhr.onload = callback;
* xhr.onerror = errback;
*
* xhr.send();
* }
*
* var promiseAjax = callbacks.lift(traditionalAjax);
* promiseAjax("GET", "/movies.json").then(console.log, console.error);
*
* var promiseAjaxGet = callbacks.lift(traditionalAjax, "GET");
* promiseAjaxGet("/movies.json").then(console.log, console.error);
*
* @param {Function} f traditional async function to be decorated
* @param {...*} [args] arguments to be prepended for the new function @deprecated
* @returns {Function} a promise-returning function
*/
function lift(f/*, args...*/) {
var args = arguments.length > 1 ? slice.call(arguments, 1) : [];
return function() {
return _apply(f, this, args.concat(slice.call(arguments)));
};
}
/**
* Lift all the functions/methods on src
* @param {object|function} src source whose functions will be lifted
* @param {function?} combine optional function for customizing the lifting
* process. It is passed dst, the lifted function, and the property name of
* the original function on src.
* @param {(object|function)?} dst option destination host onto which to place lifted
* functions. If not provided, liftAll returns a new object.
* @returns {*} If dst is provided, returns dst with lifted functions as
* properties. If dst not provided, returns a new object with lifted functions.
*/
function liftAll(src, combine, dst) {
return _liftAll(lift, combine, dst, src);
}
/**
* `promisify` is a version of `lift` that allows fine-grained control over the
* arguments that passed to the underlying function. It is intended to handle
* functions that don't follow the common callback and errback positions.
*
* The control is done by passing an object whose 'callback' and/or 'errback'
* keys, whose values are the corresponding 0-based indexes of the arguments on
* the function. Negative values are interpreted as being relative to the end
* of the arguments array.
*
* If arguments are given on the call to the 'promisified' function, they are
* intermingled with the callback and errback. If a promise is given among them,
* the execution of the function will only occur after its resolution.
*
* @example
* var delay = callbacks.promisify(setTimeout, {
* callback: 0
* });
*
* delay(100).then(function() {
* console.log("This happens 100ms afterwards");
* });
*
* @example
* function callbackAsLast(errback, followsStandards, callback) {
* if(followsStandards) {
* callback("well done!");
* } else {
* errback("some programmers just want to watch the world burn");
* }
* }
*
* var promisified = callbacks.promisify(callbackAsLast, {
* callback: -1,
* errback: 0,
* });
*
* promisified(true).then(console.log, console.error);
* promisified(false).then(console.log, console.error);
*
* @param {Function} asyncFunction traditional function to be decorated
* @param {object} positions
* @param {number} [positions.callback] index at which asyncFunction expects to
* receive a success callback
* @param {number} [positions.errback] index at which asyncFunction expects to
* receive an error callback
* @returns {function} promisified function that accepts
*
* @deprecated
*/
function promisify(asyncFunction, positions) {
return function() {
var thisArg = this;
return Promise.all(arguments).then(function(args) {
var p = Promise._defer();
var callbackPos, errbackPos;
if(typeof positions.callback === 'number') {
callbackPos = normalizePosition(args, positions.callback);
}
if(typeof positions.errback === 'number') {
errbackPos = normalizePosition(args, positions.errback);
}
if(errbackPos < callbackPos) {
insertCallback(args, errbackPos, p._handler.reject, p._handler);
insertCallback(args, callbackPos, p._handler.resolve, p._handler);
} else {
insertCallback(args, callbackPos, p._handler.resolve, p._handler);
insertCallback(args, errbackPos, p._handler.reject, p._handler);
}
asyncFunction.apply(thisArg, args);
return p;
});
};
}
function normalizePosition(args, pos) {
return pos < 0 ? (args.length + pos + 2) : pos;
}
function insertCallback(args, pos, callback, thisArg) {
if(typeof pos === 'number') {
args.splice(pos, 0, alwaysUnary(callback, thisArg));
}
}
function alwaysUnary(fn, thisArg) {
return function() {
if (arguments.length > 1) {
fn.call(thisArg, slice.call(arguments));
} else {
fn.apply(thisArg, arguments);
}
};
}
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,54 @@
/** @license MIT License (c) copyright B Cavalier & J Hann */
/**
* cancelable.js
* @deprecated
*
* Decorator that makes a deferred "cancelable". It adds a cancel() method that
* will call a special cancel handler function and then reject the deferred. The
* cancel handler can be used to do resource cleanup, or anything else that should
* be done before any other rejection handlers are executed.
*
* Usage:
*
* var cancelableDeferred = cancelable(when.defer(), myCancelHandler);
*
* @author brian@hovercraftstudios.com
*/
(function(define) {
define(function() {
/**
* Makes deferred cancelable, adding a cancel() method.
* @deprecated
*
* @param deferred {Deferred} the {@link Deferred} to make cancelable
* @param canceler {Function} cancel handler function to execute when this deferred
* is canceled. This is guaranteed to run before all other rejection handlers.
* The canceler will NOT be executed if the deferred is rejected in the standard
* way, i.e. deferred.reject(). It ONLY executes if the deferred is canceled,
* i.e. deferred.cancel()
*
* @returns deferred, with an added cancel() method.
*/
return function(deferred, canceler) {
// Add a cancel method to the deferred to reject the delegate
// with the special canceled indicator.
deferred.cancel = function() {
try {
deferred.reject(canceler(deferred));
} catch(e) {
deferred.reject(e);
}
return deferred.promise;
};
return deferred;
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(); });

View File

@ -0,0 +1,27 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* delay.js
*
* Helper that returns a promise that resolves after a delay.
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) {
define(function(require) {
var when = require('./when');
/**
* @deprecated Use when(value).delay(ms)
*/
return function delay(msec, value) {
return when(value).delay(msec);
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,13 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
/**
* ES6 global Promise shim
*/
var unhandledRejections = require('../lib/decorators/unhandledRejection');
var PromiseConstructor = unhandledRejections(require('../lib/Promise'));
module.exports = typeof global != 'undefined' ? (global.Promise = PromiseConstructor)
: typeof self != 'undefined' ? (self.Promise = PromiseConstructor)
: PromiseConstructor;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
/** @license MIT License (c) copyright 2013-2014 original author or authors */
/**
* Collection of helper functions for wrapping and executing 'traditional'
* synchronous functions in a promise interface.
*
* @author Brian Cavalier
* @contributor Renato Zannon
*/
(function(define) {
define(function(require) {
var when = require('./when');
var attempt = when['try'];
var _liftAll = require('./lib/liftAll');
var _apply = require('./lib/apply')(when.Promise);
var slice = Array.prototype.slice;
return {
lift: lift,
liftAll: liftAll,
call: attempt,
apply: apply,
compose: compose
};
/**
* Takes a function and an optional array of arguments (that might be promises),
* and calls the function. The return value is a promise whose resolution
* depends on the value returned by the function.
* @param {function} f function to be called
* @param {Array} [args] array of arguments to func
* @returns {Promise} promise for the return value of func
*/
function apply(f, args) {
// slice args just in case the caller passed an Arguments instance
return _apply(f, this, args == null ? [] : slice.call(args));
}
/**
* Takes a 'regular' function and returns a version of that function that
* returns a promise instead of a plain value, and handles thrown errors by
* returning a rejected promise. Also accepts a list of arguments to be
* prepended to the new function, as does Function.prototype.bind.
*
* The resulting function is promise-aware, in the sense that it accepts
* promise arguments, and waits for their resolution.
* @param {Function} f function to be bound
* @param {...*} [args] arguments to be prepended for the new function @deprecated
* @returns {Function} a promise-returning function
*/
function lift(f /*, args... */) {
var args = arguments.length > 1 ? slice.call(arguments, 1) : [];
return function() {
return _apply(f, this, args.concat(slice.call(arguments)));
};
}
/**
* Lift all the functions/methods on src
* @param {object|function} src source whose functions will be lifted
* @param {function?} combine optional function for customizing the lifting
* process. It is passed dst, the lifted function, and the property name of
* the original function on src.
* @param {(object|function)?} dst option destination host onto which to place lifted
* functions. If not provided, liftAll returns a new object.
* @returns {*} If dst is provided, returns dst with lifted functions as
* properties. If dst not provided, returns a new object with lifted functions.
*/
function liftAll(src, combine, dst) {
return _liftAll(lift, combine, dst, src);
}
/**
* Composes multiple functions by piping their return values. It is
* transparent to whether the functions return 'regular' values or promises:
* the piped argument is always a resolved value. If one of the functions
* throws or returns a rejected promise, the composed promise will be also
* rejected.
*
* The arguments (or promises to arguments) given to the returned function (if
* any), are passed directly to the first function on the 'pipeline'.
* @param {Function} f the function to which the arguments will be passed
* @param {...Function} [funcs] functions that will be composed, in order
* @returns {Function} a promise-returning composition of the functions
*/
function compose(f /*, funcs... */) {
var funcs = slice.call(arguments, 1);
return function() {
var thisArg = this;
var args = slice.call(arguments);
var firstPromise = attempt.apply(thisArg, [f].concat(args));
return when.reduce(funcs, function(arg, func) {
return func.call(thisArg, arg);
}, firstPromise);
};
}
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,105 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var when = require('./when');
var slice = Array.prototype.slice;
var Promise = when.Promise;
var reject = Promise.reject;
/**
* Lift a generator to create a function that can suspend and
* resume using the `yield` keyword to await promises.
* @param {function} generator
* @return {function}
*/
function lift(generator) {
return function() {
return run(generator, this, arguments);
};
}
/**
* Immediately call a generator as a promise-aware coroutine
* that can suspend and resume using the `yield` keyword to
* await promises. Additional arguments after the first will
* be passed through to the generator.
* @param {function} generator
* @returns {Promise} promise for the ultimate value returned
* from the generator.
*/
function call(generator /*x, y, z...*/) {
/*jshint validthis:true*/
return run(generator, this, slice.call(arguments, 1));
}
/**
* Immediately apply a generator, with the supplied args array,
* as a promise-aware coroutine that can suspend and resume
* using the `yield` keyword to await promises.
* @param {function} generator
* @param {Array} args arguments with which to initialize the generator
* @returns {Promise} promise for the ultimate value returned
* from the generator.
*/
function apply(generator, args) {
/*jshint validthis:true*/
return run(generator, this, args || []);
}
/**
* Helper to initiate the provided generator as a coroutine
* @returns {*}
*/
function run(generator, thisArg, args) {
return runNext(void 0, generator.apply(thisArg, args));
}
function runNext(x, iterator) {
try {
return handle(iterator.next(x), iterator);
} catch(e) {
return reject(e);
}
}
function next(x) {
/*jshint validthis:true*/
return runNext(x, this);
}
function error(e) {
/*jshint validthis:true*/
try {
return handle(this.throw(e), this);
} catch(e) {
return reject(e);
}
}
function handle(result, iterator) {
if(result.done) {
return result.value;
}
var h = Promise._handler(result.value);
if(h.state() > 0) {
return runNext(h.value, iterator);
}
var p = Promise._defer();
h.chain(p._handler, iterator, next, error);
return p;
}
return {
lift: lift,
call: call,
apply: apply
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,72 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* Generalized promise concurrency guard
* Adapted from original concept by Sakari Jokinen (Rocket Pack, Ltd.)
*
* @author Brian Cavalier
* @author John Hann
* @contributor Sakari Jokinen
*/
(function(define) {
define(function(require) {
var when = require('./when');
var slice = Array.prototype.slice;
guard.n = n;
return guard;
/**
* Creates a guarded version of f that can only be entered when the supplied
* condition allows.
* @param {function} condition represents a critical section that may only
* be entered when allowed by the condition
* @param {function} f function to guard
* @returns {function} guarded version of f
*/
function guard(condition, f) {
return function() {
var args = slice.call(arguments);
return when(condition()).withThis(this).then(function(exit) {
return when(f.apply(this, args))['finally'](exit);
});
};
}
/**
* Creates a condition that allows only n simultaneous executions
* of a guarded function
* @param {number} allowed number of allowed simultaneous executions
* @returns {function} condition function which returns a promise that
* fulfills when the critical section may be entered. The fulfillment
* value is a function ("notifyExit") that must be called when the critical
* section has been exited.
*/
function n(allowed) {
var count = 0;
var waiting = [];
return function enter() {
return when.promise(function(resolve) {
if(count < allowed) {
resolve(exit);
} else {
waiting.push(resolve);
}
count += 1;
});
};
function exit() {
count = Math.max(count - 1, 0);
if(waiting.length > 0) {
waiting.shift()(exit);
}
}
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,80 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* Licensed under the MIT License at:
* http://www.opensource.org/licenses/mit-license.php
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) { 'use strict';
define(function(require) {
var when = require('./when');
var Promise = when.Promise;
var toPromise = when.resolve;
return {
all: when.lift(all),
map: map
};
/**
* Resolve all the key-value pairs in the supplied object or promise
* for an object.
* @param {Promise|object} object or promise for object whose key-value pairs
* will be resolved
* @returns {Promise} promise for an object with the fully resolved key-value pairs
*/
function all(object) {
var p = Promise._defer();
var resolver = Promise._handler(p);
var results = {};
var keys = Object.keys(object);
var pending = keys.length;
for(var i=0, k; i<keys.length; ++i) {
k = keys[i];
Promise._handler(object[k]).fold(settleKey, k, results, resolver);
}
if(pending === 0) {
resolver.resolve(results);
}
return p;
function settleKey(k, x, resolver) {
/*jshint validthis:true*/
this[k] = x;
if(--pending === 0) {
resolver.resolve(results);
}
}
}
/**
* Map values in the supplied object's keys
* @param {Promise|object} object or promise for object whose key-value pairs
* will be reduced
* @param {function(value:*, key:String):*} f mapping function which may
* return either a promise or a value
* @returns {Promise} promise for an object with the mapped and fully
* resolved key-value pairs
*/
function map(object, f) {
return toPromise(object).then(function(object) {
return all(Object.keys(object).reduce(function(o, k) {
o[k] = toPromise(object[k]).fold(mapWithKey, k);
return o;
}, {}));
});
function mapWithKey(k, x) {
return f(x, k);
}
}
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,17 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function (require) {
var makePromise = require('./makePromise');
var Scheduler = require('./Scheduler');
var async = require('./env').asap;
return makePromise({
scheduler: new Scheduler(async)
});
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,80 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
// Credit to Twisol (https://github.com/Twisol) for suggesting
// this type of extensible queue + trampoline approach for next-tick conflation.
/**
* Async task scheduler
* @param {function} async function to schedule a single async function
* @constructor
*/
function Scheduler(async) {
this._async = async;
this._running = false;
this._queue = this;
this._queueLen = 0;
this._afterQueue = {};
this._afterQueueLen = 0;
var self = this;
this.drain = function() {
self._drain();
};
}
/**
* Enqueue a task
* @param {{ run:function }} task
*/
Scheduler.prototype.enqueue = function(task) {
this._queue[this._queueLen++] = task;
this.run();
};
/**
* Enqueue a task to run after the main task queue
* @param {{ run:function }} task
*/
Scheduler.prototype.afterQueue = function(task) {
this._afterQueue[this._afterQueueLen++] = task;
this.run();
};
Scheduler.prototype.run = function() {
if (!this._running) {
this._running = true;
this._async(this.drain);
}
};
/**
* Drain the handler queue entirely, and then the after queue
*/
Scheduler.prototype._drain = function() {
var i = 0;
for (; i < this._queueLen; ++i) {
this._queue[i].run();
this._queue[i] = void 0;
}
this._queueLen = 0;
this._running = false;
for (i = 0; i < this._afterQueueLen; ++i) {
this._afterQueue[i].run();
this._afterQueue[i] = void 0;
}
this._afterQueueLen = 0;
};
return Scheduler;
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,27 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
/**
* Custom error type for promises rejected by promise.timeout
* @param {string} message
* @constructor
*/
function TimeoutError (message) {
Error.call(this);
this.message = message;
this.name = TimeoutError.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, TimeoutError);
}
}
TimeoutError.prototype = Object.create(Error.prototype);
TimeoutError.prototype.constructor = TimeoutError;
return TimeoutError;
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,55 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
makeApply.tryCatchResolve = tryCatchResolve;
return makeApply;
function makeApply(Promise, call) {
if(arguments.length < 2) {
call = tryCatchResolve;
}
return apply;
function apply(f, thisArg, args) {
var p = Promise._defer();
var l = args.length;
var params = new Array(l);
callAndResolve({ f:f, thisArg:thisArg, args:args, params:params, i:l-1, call:call }, p._handler);
return p;
}
function callAndResolve(c, h) {
if(c.i < 0) {
return call(c.f, c.thisArg, c.params, h);
}
var handler = Promise._handler(c.args[c.i]);
handler.fold(callAndResolveNext, c, void 0, h);
}
function callAndResolveNext(c, x, h) {
c.params[c.i] = x;
c.i -= 1;
callAndResolve(c, h);
}
}
function tryCatchResolve(f, thisArg, args, resolver) {
try {
resolver.resolve(f.apply(thisArg, args));
} catch(e) {
resolver.reject(e);
}
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,289 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var state = require('../state');
var applier = require('../apply');
return function array(Promise) {
var applyFold = applier(Promise);
var toPromise = Promise.resolve;
var all = Promise.all;
var ar = Array.prototype.reduce;
var arr = Array.prototype.reduceRight;
var slice = Array.prototype.slice;
// Additional array combinators
Promise.any = any;
Promise.some = some;
Promise.settle = settle;
Promise.map = map;
Promise.filter = filter;
Promise.reduce = reduce;
Promise.reduceRight = reduceRight;
/**
* When this promise fulfills with an array, do
* onFulfilled.apply(void 0, array)
* @param {function} onFulfilled function to apply
* @returns {Promise} promise for the result of applying onFulfilled
*/
Promise.prototype.spread = function(onFulfilled) {
return this.then(all).then(function(array) {
return onFulfilled.apply(this, array);
});
};
return Promise;
/**
* One-winner competitive race.
* Return a promise that will fulfill when one of the promises
* in the input array fulfills, or will reject when all promises
* have rejected.
* @param {array} promises
* @returns {Promise} promise for the first fulfilled value
*/
function any(promises) {
var p = Promise._defer();
var resolver = p._handler;
var l = promises.length>>>0;
var pending = l;
var errors = [];
for (var h, x, i = 0; i < l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
--pending;
continue;
}
h = Promise._handler(x);
if(h.state() > 0) {
resolver.become(h);
Promise._visitRemaining(promises, i, h);
break;
} else {
h.visit(resolver, handleFulfill, handleReject);
}
}
if(pending === 0) {
resolver.reject(new RangeError('any(): array must not be empty'));
}
return p;
function handleFulfill(x) {
/*jshint validthis:true*/
errors = null;
this.resolve(x); // this === resolver
}
function handleReject(e) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
errors.push(e);
if(--pending === 0) {
this.reject(errors);
}
}
}
/**
* N-winner competitive race
* Return a promise that will fulfill when n input promises have
* fulfilled, or will reject when it becomes impossible for n
* input promises to fulfill (ie when promises.length - n + 1
* have rejected)
* @param {array} promises
* @param {number} n
* @returns {Promise} promise for the earliest n fulfillment values
*
* @deprecated
*/
function some(promises, n) {
/*jshint maxcomplexity:7*/
var p = Promise._defer();
var resolver = p._handler;
var results = [];
var errors = [];
var l = promises.length>>>0;
var nFulfill = 0;
var nReject;
var x, i; // reused in both for() loops
// First pass: count actual array items
for(i=0; i<l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
continue;
}
++nFulfill;
}
// Compute actual goals
n = Math.max(n, 0);
nReject = (nFulfill - n + 1);
nFulfill = Math.min(n, nFulfill);
if(n > nFulfill) {
resolver.reject(new RangeError('some(): array must contain at least '
+ n + ' item(s), but had ' + nFulfill));
} else if(nFulfill === 0) {
resolver.resolve(results);
}
// Second pass: observe each array item, make progress toward goals
for(i=0; i<l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
continue;
}
Promise._handler(x).visit(resolver, fulfill, reject, resolver.notify);
}
return p;
function fulfill(x) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
results.push(x);
if(--nFulfill === 0) {
errors = null;
this.resolve(results);
}
}
function reject(e) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
errors.push(e);
if(--nReject === 0) {
results = null;
this.reject(errors);
}
}
}
/**
* Apply f to the value of each promise in a list of promises
* and return a new list containing the results.
* @param {array} promises
* @param {function(x:*, index:Number):*} f mapping function
* @returns {Promise}
*/
function map(promises, f) {
return Promise._traverse(f, promises);
}
/**
* Filter the provided array of promises using the provided predicate. Input may
* contain promises and values
* @param {Array} promises array of promises and values
* @param {function(x:*, index:Number):boolean} predicate filtering predicate.
* Must return truthy (or promise for truthy) for items to retain.
* @returns {Promise} promise that will fulfill with an array containing all items
* for which predicate returned truthy.
*/
function filter(promises, predicate) {
var a = slice.call(promises);
return Promise._traverse(predicate, a).then(function(keep) {
return filterSync(a, keep);
});
}
function filterSync(promises, keep) {
// Safe because we know all promises have fulfilled if we've made it this far
var l = keep.length;
var filtered = new Array(l);
for(var i=0, j=0; i<l; ++i) {
if(keep[i]) {
filtered[j++] = Promise._handler(promises[i]).value;
}
}
filtered.length = j;
return filtered;
}
/**
* Return a promise that will always fulfill with an array containing
* the outcome states of all input promises. The returned promise
* will never reject.
* @param {Array} promises
* @returns {Promise} promise for array of settled state descriptors
*/
function settle(promises) {
return all(promises.map(settleOne));
}
function settleOne(p) {
var h = Promise._handler(p);
if(h.state() === 0) {
return toPromise(p).then(state.fulfilled, state.rejected);
}
h._unreport();
return state.inspect(h);
}
/**
* Traditional reduce function, similar to `Array.prototype.reduce()`, but
* input may contain promises and/or values, and reduceFunc
* may return either a value or a promise, *and* initialValue may
* be a promise for the starting value.
* @param {Array|Promise} promises array or promise for an array of anything,
* may contain a mix of promises and values.
* @param {function(accumulated:*, x:*, index:Number):*} f reduce function
* @returns {Promise} that will resolve to the final reduced value
*/
function reduce(promises, f /*, initialValue */) {
return arguments.length > 2 ? ar.call(promises, liftCombine(f), arguments[2])
: ar.call(promises, liftCombine(f));
}
/**
* Traditional reduce function, similar to `Array.prototype.reduceRight()`, but
* input may contain promises and/or values, and reduceFunc
* may return either a value or a promise, *and* initialValue may
* be a promise for the starting value.
* @param {Array|Promise} promises array or promise for an array of anything,
* may contain a mix of promises and values.
* @param {function(accumulated:*, x:*, index:Number):*} f reduce function
* @returns {Promise} that will resolve to the final reduced value
*/
function reduceRight(promises, f /*, initialValue */) {
return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2])
: arr.call(promises, liftCombine(f));
}
function liftCombine(f) {
return function(z, x, i) {
return applyFold(f, void 0, [z,x,i]);
};
}
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,160 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function flow(Promise) {
var resolve = Promise.resolve;
var reject = Promise.reject;
var origCatch = Promise.prototype['catch'];
/**
* Handle the ultimate fulfillment value or rejection reason, and assume
* responsibility for all errors. If an error propagates out of result
* or handleFatalError, it will be rethrown to the host, resulting in a
* loud stack track on most platforms and a crash on some.
* @param {function?} onResult
* @param {function?} onError
* @returns {undefined}
*/
Promise.prototype.done = function(onResult, onError) {
this._handler.visit(this._handler.receiver, onResult, onError);
};
/**
* Add Error-type and predicate matching to catch. Examples:
* promise.catch(TypeError, handleTypeError)
* .catch(predicate, handleMatchedErrors)
* .catch(handleRemainingErrors)
* @param onRejected
* @returns {*}
*/
Promise.prototype['catch'] = Promise.prototype.otherwise = function(onRejected) {
if (arguments.length < 2) {
return origCatch.call(this, onRejected);
}
if(typeof onRejected !== 'function') {
return this.ensure(rejectInvalidPredicate);
}
return origCatch.call(this, createCatchFilter(arguments[1], onRejected));
};
/**
* Wraps the provided catch handler, so that it will only be called
* if the predicate evaluates truthy
* @param {?function} handler
* @param {function} predicate
* @returns {function} conditional catch handler
*/
function createCatchFilter(handler, predicate) {
return function(e) {
return evaluatePredicate(e, predicate)
? handler.call(this, e)
: reject(e);
};
}
/**
* Ensures that onFulfilledOrRejected will be called regardless of whether
* this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT
* receive the promises' value or reason. Any returned value will be disregarded.
* onFulfilledOrRejected may throw or return a rejected promise to signal
* an additional error.
* @param {function} handler handler to be called regardless of
* fulfillment or rejection
* @returns {Promise}
*/
Promise.prototype['finally'] = Promise.prototype.ensure = function(handler) {
if(typeof handler !== 'function') {
return this;
}
return this.then(function(x) {
return runSideEffect(handler, this, identity, x);
}, function(e) {
return runSideEffect(handler, this, reject, e);
});
};
function runSideEffect (handler, thisArg, propagate, value) {
var result = handler.call(thisArg);
return maybeThenable(result)
? propagateValue(result, propagate, value)
: propagate(value);
}
function propagateValue (result, propagate, x) {
return resolve(result).then(function () {
return propagate(x);
});
}
/**
* Recover from a failure by returning a defaultValue. If defaultValue
* is a promise, it's fulfillment value will be used. If defaultValue is
* a promise that rejects, the returned promise will reject with the
* same reason.
* @param {*} defaultValue
* @returns {Promise} new promise
*/
Promise.prototype['else'] = Promise.prototype.orElse = function(defaultValue) {
return this.then(void 0, function() {
return defaultValue;
});
};
/**
* Shortcut for .then(function() { return value; })
* @param {*} value
* @return {Promise} a promise that:
* - is fulfilled if value is not a promise, or
* - if value is a promise, will fulfill with its value, or reject
* with its reason.
*/
Promise.prototype['yield'] = function(value) {
return this.then(function() {
return value;
});
};
/**
* Runs a side effect when this promise fulfills, without changing the
* fulfillment value.
* @param {function} onFulfilledSideEffect
* @returns {Promise}
*/
Promise.prototype.tap = function(onFulfilledSideEffect) {
return this.then(onFulfilledSideEffect)['yield'](this);
};
return Promise;
};
function rejectInvalidPredicate() {
throw new TypeError('catch predicate must be a function');
}
function evaluatePredicate(e, predicate) {
return isError(predicate) ? e instanceof predicate : predicate(e);
}
function isError(predicate) {
return predicate === Error
|| (predicate != null && predicate.prototype instanceof Error);
}
function maybeThenable(x) {
return (typeof x === 'object' || typeof x === 'function') && x !== null;
}
function identity(x) {
return x;
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,27 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
/** @author Jeff Escalante */
(function(define) { 'use strict';
define(function() {
return function fold(Promise) {
Promise.prototype.fold = function(f, z) {
var promise = this._beget();
this._handler.fold(function(z, x, to) {
Promise._handler(z).fold(function(x, z, to) {
to.resolve(f.call(this, z, x));
}, x, this, to);
}, z, promise._handler.receiver, promise._handler);
return promise;
};
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,20 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var inspect = require('../state').inspect;
return function inspection(Promise) {
Promise.prototype.inspect = function() {
return inspect(Promise._handler(this));
};
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,65 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function generate(Promise) {
var resolve = Promise.resolve;
Promise.iterate = iterate;
Promise.unfold = unfold;
return Promise;
/**
* @deprecated Use github.com/cujojs/most streams and most.iterate
* Generate a (potentially infinite) stream of promised values:
* x, f(x), f(f(x)), etc. until condition(x) returns true
* @param {function} f function to generate a new x from the previous x
* @param {function} condition function that, given the current x, returns
* truthy when the iterate should stop
* @param {function} handler function to handle the value produced by f
* @param {*|Promise} x starting value, may be a promise
* @return {Promise} the result of the last call to f before
* condition returns true
*/
function iterate(f, condition, handler, x) {
return unfold(function(x) {
return [x, f(x)];
}, condition, handler, x);
}
/**
* @deprecated Use github.com/cujojs/most streams and most.unfold
* Generate a (potentially infinite) stream of promised values
* by applying handler(generator(seed)) iteratively until
* condition(seed) returns true.
* @param {function} unspool function that generates a [value, newSeed]
* given a seed.
* @param {function} condition function that, given the current seed, returns
* truthy when the unfold should stop
* @param {function} handler function to handle the value produced by unspool
* @param x {*|Promise} starting value, may be a promise
* @return {Promise} the result of the last value produced by unspool before
* condition returns true
*/
function unfold(unspool, condition, handler, x) {
return resolve(x).then(function(seed) {
return resolve(condition(seed)).then(function(done) {
return done ? seed : resolve(unspool(seed)).spread(next);
});
});
function next(item, newSeed) {
return resolve(handler(item)).then(function() {
return unfold(unspool, condition, handler, newSeed);
});
}
}
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,24 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function progress(Promise) {
/**
* @deprecated
* Register a progress handler for this promise
* @param {function} onProgress
* @returns {Promise}
*/
Promise.prototype.progress = function(onProgress) {
return this.then(void 0, void 0, onProgress);
};
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,78 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var env = require('../env');
var TimeoutError = require('../TimeoutError');
function setTimeout(f, ms, x, y) {
return env.setTimer(function() {
f(x, y, ms);
}, ms);
}
return function timed(Promise) {
/**
* Return a new promise whose fulfillment value is revealed only
* after ms milliseconds
* @param {number} ms milliseconds
* @returns {Promise}
*/
Promise.prototype.delay = function(ms) {
var p = this._beget();
this._handler.fold(handleDelay, ms, void 0, p._handler);
return p;
};
function handleDelay(ms, x, h) {
setTimeout(resolveDelay, ms, x, h);
}
function resolveDelay(x, h) {
h.resolve(x);
}
/**
* Return a new promise that rejects after ms milliseconds unless
* this promise fulfills earlier, in which case the returned promise
* fulfills with the same value.
* @param {number} ms milliseconds
* @param {Error|*=} reason optional rejection reason to use, defaults
* to a TimeoutError if not provided
* @returns {Promise}
*/
Promise.prototype.timeout = function(ms, reason) {
var p = this._beget();
var h = p._handler;
var t = setTimeout(onTimeout, ms, reason, p._handler);
this._handler.visit(h,
function onFulfill(x) {
env.clearTimer(t);
this.resolve(x); // this = h
},
function onReject(x) {
env.clearTimer(t);
this.reject(x); // this = h
},
h.notify);
return p;
};
function onTimeout(reason, h, ms) {
var e = typeof reason === 'undefined'
? new TimeoutError('timed out after ' + ms + 'ms')
: reason;
h.reject(e);
}
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,86 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var setTimer = require('../env').setTimer;
var format = require('../format');
return function unhandledRejection(Promise) {
var logError = noop;
var logInfo = noop;
var localConsole;
if(typeof console !== 'undefined') {
// Alias console to prevent things like uglify's drop_console option from
// removing console.log/error. Unhandled rejections fall into the same
// category as uncaught exceptions, and build tools shouldn't silence them.
localConsole = console;
logError = typeof localConsole.error !== 'undefined'
? function (e) { localConsole.error(e); }
: function (e) { localConsole.log(e); };
logInfo = typeof localConsole.info !== 'undefined'
? function (e) { localConsole.info(e); }
: function (e) { localConsole.log(e); };
}
Promise.onPotentiallyUnhandledRejection = function(rejection) {
enqueue(report, rejection);
};
Promise.onPotentiallyUnhandledRejectionHandled = function(rejection) {
enqueue(unreport, rejection);
};
Promise.onFatalRejection = function(rejection) {
enqueue(throwit, rejection.value);
};
var tasks = [];
var reported = [];
var running = null;
function report(r) {
if(!r.handled) {
reported.push(r);
logError('Potentially unhandled rejection [' + r.id + '] ' + format.formatError(r.value));
}
}
function unreport(r) {
var i = reported.indexOf(r);
if(i >= 0) {
reported.splice(i, 1);
logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value));
}
}
function enqueue(f, x) {
tasks.push(f, x);
if(running === null) {
running = setTimer(flush, 0);
}
}
function flush() {
running = null;
while(tasks.length > 0) {
tasks.shift()(tasks.shift());
}
}
return Promise;
};
function throwit(e) {
throw e;
}
function noop() {}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,38 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function addWith(Promise) {
/**
* Returns a promise whose handlers will be called with `this` set to
* the supplied receiver. Subsequent promises derived from the
* returned promise will also have their handlers called with receiver
* as `this`. Calling `with` with undefined or no arguments will return
* a promise whose handlers will again be called in the usual Promises/A+
* way (no `this`) thus safely undoing any previous `with` in the
* promise chain.
*
* WARNING: Promises returned from `with`/`withThis` are NOT Promises/A+
* compliant, specifically violating 2.2.5 (http://promisesaplus.com/#point-41)
*
* @param {object} receiver `this` value for all handlers attached to
* the returned promise.
* @returns {Promise}
*/
Promise.prototype['with'] = Promise.prototype.withThis = function(receiver) {
var p = this._beget();
var child = p._handler;
child.receiver = receiver;
this._handler.chain(child, receiver);
return p;
};
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,73 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
/*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/
(function(define) { 'use strict';
define(function(require) {
/*jshint maxcomplexity:6*/
// Sniff "best" async scheduling option
// Prefer process.nextTick or MutationObserver, then check for
// setTimeout, and finally vertx, since its the only env that doesn't
// have setTimeout
var MutationObs;
var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout;
// Default env
var setTimer = function(f, ms) { return setTimeout(f, ms); };
var clearTimer = function(t) { return clearTimeout(t); };
var asap = function (f) { return capturedSetTimeout(f, 0); };
// Detect specific env
if (isNode()) { // Node
asap = function (f) { return process.nextTick(f); };
} else if (MutationObs = hasMutationObserver()) { // Modern browser
asap = initMutationObserver(MutationObs);
} else if (!capturedSetTimeout) { // vert.x
var vertxRequire = require;
var vertx = vertxRequire('vertx');
setTimer = function (f, ms) { return vertx.setTimer(ms, f); };
clearTimer = vertx.cancelTimer;
asap = vertx.runOnLoop || vertx.runOnContext;
}
return {
setTimer: setTimer,
clearTimer: clearTimer,
asap: asap
};
function isNode () {
return typeof process !== 'undefined' &&
Object.prototype.toString.call(process) === '[object process]';
}
function hasMutationObserver () {
return (typeof MutationObserver === 'function' && MutationObserver) ||
(typeof WebKitMutationObserver === 'function' && WebKitMutationObserver);
}
function initMutationObserver(MutationObserver) {
var scheduled;
var node = document.createTextNode('');
var o = new MutationObserver(run);
o.observe(node, { characterData: true });
function run() {
var f = scheduled;
scheduled = void 0;
f();
}
var i = 0;
return function (f) {
scheduled = f;
node.data = (i ^= 1);
};
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,56 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return {
formatError: formatError,
formatObject: formatObject,
tryStringify: tryStringify
};
/**
* Format an error into a string. If e is an Error and has a stack property,
* it's returned. Otherwise, e is formatted using formatObject, with a
* warning added about e not being a proper Error.
* @param {*} e
* @returns {String} formatted string, suitable for output to developers
*/
function formatError(e) {
var s = typeof e === 'object' && e !== null && e.stack ? e.stack : formatObject(e);
return e instanceof Error ? s : s + ' (WARNING: non-Error used)';
}
/**
* Format an object, detecting "plain" objects and running them through
* JSON.stringify if possible.
* @param {Object} o
* @returns {string}
*/
function formatObject(o) {
var s = String(o);
if(s === '[object Object]' && typeof JSON !== 'undefined') {
s = tryStringify(o, s);
}
return s;
}
/**
* Try to return the result of JSON.stringify(x). If that fails, return
* defaultValue
* @param {*} x
* @param {*} defaultValue
* @returns {String|*} JSON.stringify(x) or defaultValue
*/
function tryStringify(x, defaultValue) {
try {
return JSON.stringify(x);
} catch(e) {
return defaultValue;
}
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,28 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function liftAll(liftOne, combine, dst, src) {
if(typeof combine === 'undefined') {
combine = defaultCombine;
}
return Object.keys(src).reduce(function(dst, key) {
var f = src[key];
return typeof f === 'function' ? combine(dst, liftOne(f), key) : dst;
}, typeof dst === 'undefined' ? defaultDst(src) : dst);
};
function defaultCombine(o, f, k) {
o[k] = f;
return o;
}
function defaultDst(src) {
return typeof src === 'function' ? src.bind() : Object.create(src);
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,927 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return function makePromise(environment) {
var tasks = environment.scheduler;
var emitRejection = initEmitRejection();
var objectCreate = Object.create ||
function(proto) {
function Child() {}
Child.prototype = proto;
return new Child();
};
/**
* Create a promise whose fate is determined by resolver
* @constructor
* @returns {Promise} promise
* @name Promise
*/
function Promise(resolver, handler) {
this._handler = resolver === Handler ? handler : init(resolver);
}
/**
* Run the supplied resolver
* @param resolver
* @returns {Pending}
*/
function init(resolver) {
var handler = new Pending();
try {
resolver(promiseResolve, promiseReject, promiseNotify);
} catch (e) {
promiseReject(e);
}
return handler;
/**
* Transition from pre-resolution state to post-resolution state, notifying
* all listeners of the ultimate fulfillment or rejection
* @param {*} x resolution value
*/
function promiseResolve (x) {
handler.resolve(x);
}
/**
* Reject this promise with reason, which will be used verbatim
* @param {Error|*} reason rejection reason, strongly suggested
* to be an Error type
*/
function promiseReject (reason) {
handler.reject(reason);
}
/**
* @deprecated
* Issue a progress event, notifying all progress listeners
* @param {*} x progress event payload to pass to all listeners
*/
function promiseNotify (x) {
handler.notify(x);
}
}
// Creation
Promise.resolve = resolve;
Promise.reject = reject;
Promise.never = never;
Promise._defer = defer;
Promise._handler = getHandler;
/**
* Returns a trusted promise. If x is already a trusted promise, it is
* returned, otherwise returns a new trusted Promise which follows x.
* @param {*} x
* @return {Promise} promise
*/
function resolve(x) {
return isPromise(x) ? x
: new Promise(Handler, new Async(getHandler(x)));
}
/**
* Return a reject promise with x as its reason (x is used verbatim)
* @param {*} x
* @returns {Promise} rejected promise
*/
function reject(x) {
return new Promise(Handler, new Async(new Rejected(x)));
}
/**
* Return a promise that remains pending forever
* @returns {Promise} forever-pending promise.
*/
function never() {
return foreverPendingPromise; // Should be frozen
}
/**
* Creates an internal {promise, resolver} pair
* @private
* @returns {Promise}
*/
function defer() {
return new Promise(Handler, new Pending());
}
// Transformation and flow control
/**
* Transform this promise's fulfillment value, returning a new Promise
* for the transformed result. If the promise cannot be fulfilled, onRejected
* is called with the reason. onProgress *may* be called with updates toward
* this promise's fulfillment.
* @param {function=} onFulfilled fulfillment handler
* @param {function=} onRejected rejection handler
* @param {function=} onProgress @deprecated progress handler
* @return {Promise} new promise
*/
Promise.prototype.then = function(onFulfilled, onRejected, onProgress) {
var parent = this._handler;
var state = parent.join().state();
if ((typeof onFulfilled !== 'function' && state > 0) ||
(typeof onRejected !== 'function' && state < 0)) {
// Short circuit: value will not change, simply share handler
return new this.constructor(Handler, parent);
}
var p = this._beget();
var child = p._handler;
parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress);
return p;
};
/**
* If this promise cannot be fulfilled due to an error, call onRejected to
* handle the error. Shortcut for .then(undefined, onRejected)
* @param {function?} onRejected
* @return {Promise}
*/
Promise.prototype['catch'] = function(onRejected) {
return this.then(void 0, onRejected);
};
/**
* Creates a new, pending promise of the same type as this promise
* @private
* @returns {Promise}
*/
Promise.prototype._beget = function() {
return begetFrom(this._handler, this.constructor);
};
function begetFrom(parent, Promise) {
var child = new Pending(parent.receiver, parent.join().context);
return new Promise(Handler, child);
}
// Array combinators
Promise.all = all;
Promise.race = race;
Promise._traverse = traverse;
/**
* Return a promise that will fulfill when all promises in the
* input array have fulfilled, or will reject when one of the
* promises rejects.
* @param {array} promises array of promises
* @returns {Promise} promise for array of fulfillment values
*/
function all(promises) {
return traverseWith(snd, null, promises);
}
/**
* Array<Promise<X>> -> Promise<Array<f(X)>>
* @private
* @param {function} f function to apply to each promise's value
* @param {Array} promises array of promises
* @returns {Promise} promise for transformed values
*/
function traverse(f, promises) {
return traverseWith(tryCatch2, f, promises);
}
function traverseWith(tryMap, f, promises) {
var handler = typeof f === 'function' ? mapAt : settleAt;
var resolver = new Pending();
var pending = promises.length >>> 0;
var results = new Array(pending);
for (var i = 0, x; i < promises.length && !resolver.resolved; ++i) {
x = promises[i];
if (x === void 0 && !(i in promises)) {
--pending;
continue;
}
traverseAt(promises, handler, i, x, resolver);
}
if(pending === 0) {
resolver.become(new Fulfilled(results));
}
return new Promise(Handler, resolver);
function mapAt(i, x, resolver) {
if(!resolver.resolved) {
traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver);
}
}
function settleAt(i, x, resolver) {
results[i] = x;
if(--pending === 0) {
resolver.become(new Fulfilled(results));
}
}
}
function traverseAt(promises, handler, i, x, resolver) {
if (maybeThenable(x)) {
var h = getHandlerMaybeThenable(x);
var s = h.state();
if (s === 0) {
h.fold(handler, i, void 0, resolver);
} else if (s > 0) {
handler(i, h.value, resolver);
} else {
resolver.become(h);
visitRemaining(promises, i+1, h);
}
} else {
handler(i, x, resolver);
}
}
Promise._visitRemaining = visitRemaining;
function visitRemaining(promises, start, handler) {
for(var i=start; i<promises.length; ++i) {
markAsHandled(getHandler(promises[i]), handler);
}
}
function markAsHandled(h, handler) {
if(h === handler) {
return;
}
var s = h.state();
if(s === 0) {
h.visit(h, void 0, h._unreport);
} else if(s < 0) {
h._unreport();
}
}
/**
* Fulfill-reject competitive race. Return a promise that will settle
* to the same state as the earliest input promise to settle.
*
* WARNING: The ES6 Promise spec requires that race()ing an empty array
* must return a promise that is pending forever. This implementation
* returns a singleton forever-pending promise, the same singleton that is
* returned by Promise.never(), thus can be checked with ===
*
* @param {array} promises array of promises to race
* @returns {Promise} if input is non-empty, a promise that will settle
* to the same outcome as the earliest input promise to settle. if empty
* is empty, returns a promise that will never settle.
*/
function race(promises) {
if(typeof promises !== 'object' || promises === null) {
return reject(new TypeError('non-iterable passed to race()'));
}
// Sigh, race([]) is untestable unless we return *something*
// that is recognizable without calling .then() on it.
return promises.length === 0 ? never()
: promises.length === 1 ? resolve(promises[0])
: runRace(promises);
}
function runRace(promises) {
var resolver = new Pending();
var i, x, h;
for(i=0; i<promises.length; ++i) {
x = promises[i];
if (x === void 0 && !(i in promises)) {
continue;
}
h = getHandler(x);
if(h.state() !== 0) {
resolver.become(h);
visitRemaining(promises, i+1, h);
break;
} else {
h.visit(resolver, resolver.resolve, resolver.reject);
}
}
return new Promise(Handler, resolver);
}
// Promise internals
// Below this, everything is @private
/**
* Get an appropriate handler for x, without checking for cycles
* @param {*} x
* @returns {object} handler
*/
function getHandler(x) {
if(isPromise(x)) {
return x._handler.join();
}
return maybeThenable(x) ? getHandlerUntrusted(x) : new Fulfilled(x);
}
/**
* Get a handler for thenable x.
* NOTE: You must only call this if maybeThenable(x) == true
* @param {object|function|Promise} x
* @returns {object} handler
*/
function getHandlerMaybeThenable(x) {
return isPromise(x) ? x._handler.join() : getHandlerUntrusted(x);
}
/**
* Get a handler for potentially untrusted thenable x
* @param {*} x
* @returns {object} handler
*/
function getHandlerUntrusted(x) {
try {
var untrustedThen = x.then;
return typeof untrustedThen === 'function'
? new Thenable(untrustedThen, x)
: new Fulfilled(x);
} catch(e) {
return new Rejected(e);
}
}
/**
* Handler for a promise that is pending forever
* @constructor
*/
function Handler() {}
Handler.prototype.when
= Handler.prototype.become
= Handler.prototype.notify // deprecated
= Handler.prototype.fail
= Handler.prototype._unreport
= Handler.prototype._report
= noop;
Handler.prototype._state = 0;
Handler.prototype.state = function() {
return this._state;
};
/**
* Recursively collapse handler chain to find the handler
* nearest to the fully resolved value.
* @returns {object} handler nearest the fully resolved value
*/
Handler.prototype.join = function() {
var h = this;
while(h.handler !== void 0) {
h = h.handler;
}
return h;
};
Handler.prototype.chain = function(to, receiver, fulfilled, rejected, progress) {
this.when({
resolver: to,
receiver: receiver,
fulfilled: fulfilled,
rejected: rejected,
progress: progress
});
};
Handler.prototype.visit = function(receiver, fulfilled, rejected, progress) {
this.chain(failIfRejected, receiver, fulfilled, rejected, progress);
};
Handler.prototype.fold = function(f, z, c, to) {
this.when(new Fold(f, z, c, to));
};
/**
* Handler that invokes fail() on any handler it becomes
* @constructor
*/
function FailIfRejected() {}
inherit(Handler, FailIfRejected);
FailIfRejected.prototype.become = function(h) {
h.fail();
};
var failIfRejected = new FailIfRejected();
/**
* Handler that manages a queue of consumers waiting on a pending promise
* @constructor
*/
function Pending(receiver, inheritedContext) {
Promise.createContext(this, inheritedContext);
this.consumers = void 0;
this.receiver = receiver;
this.handler = void 0;
this.resolved = false;
}
inherit(Handler, Pending);
Pending.prototype._state = 0;
Pending.prototype.resolve = function(x) {
this.become(getHandler(x));
};
Pending.prototype.reject = function(x) {
if(this.resolved) {
return;
}
this.become(new Rejected(x));
};
Pending.prototype.join = function() {
if (!this.resolved) {
return this;
}
var h = this;
while (h.handler !== void 0) {
h = h.handler;
if (h === this) {
return this.handler = cycle();
}
}
return h;
};
Pending.prototype.run = function() {
var q = this.consumers;
var handler = this.handler;
this.handler = this.handler.join();
this.consumers = void 0;
for (var i = 0; i < q.length; ++i) {
handler.when(q[i]);
}
};
Pending.prototype.become = function(handler) {
if(this.resolved) {
return;
}
this.resolved = true;
this.handler = handler;
if(this.consumers !== void 0) {
tasks.enqueue(this);
}
if(this.context !== void 0) {
handler._report(this.context);
}
};
Pending.prototype.when = function(continuation) {
if(this.resolved) {
tasks.enqueue(new ContinuationTask(continuation, this.handler));
} else {
if(this.consumers === void 0) {
this.consumers = [continuation];
} else {
this.consumers.push(continuation);
}
}
};
/**
* @deprecated
*/
Pending.prototype.notify = function(x) {
if(!this.resolved) {
tasks.enqueue(new ProgressTask(x, this));
}
};
Pending.prototype.fail = function(context) {
var c = typeof context === 'undefined' ? this.context : context;
this.resolved && this.handler.join().fail(c);
};
Pending.prototype._report = function(context) {
this.resolved && this.handler.join()._report(context);
};
Pending.prototype._unreport = function() {
this.resolved && this.handler.join()._unreport();
};
/**
* Wrap another handler and force it into a future stack
* @param {object} handler
* @constructor
*/
function Async(handler) {
this.handler = handler;
}
inherit(Handler, Async);
Async.prototype.when = function(continuation) {
tasks.enqueue(new ContinuationTask(continuation, this));
};
Async.prototype._report = function(context) {
this.join()._report(context);
};
Async.prototype._unreport = function() {
this.join()._unreport();
};
/**
* Handler that wraps an untrusted thenable and assimilates it in a future stack
* @param {function} then
* @param {{then: function}} thenable
* @constructor
*/
function Thenable(then, thenable) {
Pending.call(this);
tasks.enqueue(new AssimilateTask(then, thenable, this));
}
inherit(Pending, Thenable);
/**
* Handler for a fulfilled promise
* @param {*} x fulfillment value
* @constructor
*/
function Fulfilled(x) {
Promise.createContext(this);
this.value = x;
}
inherit(Handler, Fulfilled);
Fulfilled.prototype._state = 1;
Fulfilled.prototype.fold = function(f, z, c, to) {
runContinuation3(f, z, this, c, to);
};
Fulfilled.prototype.when = function(cont) {
runContinuation1(cont.fulfilled, this, cont.receiver, cont.resolver);
};
var errorId = 0;
/**
* Handler for a rejected promise
* @param {*} x rejection reason
* @constructor
*/
function Rejected(x) {
Promise.createContext(this);
this.id = ++errorId;
this.value = x;
this.handled = false;
this.reported = false;
this._report();
}
inherit(Handler, Rejected);
Rejected.prototype._state = -1;
Rejected.prototype.fold = function(f, z, c, to) {
to.become(this);
};
Rejected.prototype.when = function(cont) {
if(typeof cont.rejected === 'function') {
this._unreport();
}
runContinuation1(cont.rejected, this, cont.receiver, cont.resolver);
};
Rejected.prototype._report = function(context) {
tasks.afterQueue(new ReportTask(this, context));
};
Rejected.prototype._unreport = function() {
if(this.handled) {
return;
}
this.handled = true;
tasks.afterQueue(new UnreportTask(this));
};
Rejected.prototype.fail = function(context) {
this.reported = true;
emitRejection('unhandledRejection', this);
Promise.onFatalRejection(this, context === void 0 ? this.context : context);
};
function ReportTask(rejection, context) {
this.rejection = rejection;
this.context = context;
}
ReportTask.prototype.run = function() {
if(!this.rejection.handled && !this.rejection.reported) {
this.rejection.reported = true;
emitRejection('unhandledRejection', this.rejection) ||
Promise.onPotentiallyUnhandledRejection(this.rejection, this.context);
}
};
function UnreportTask(rejection) {
this.rejection = rejection;
}
UnreportTask.prototype.run = function() {
if(this.rejection.reported) {
emitRejection('rejectionHandled', this.rejection) ||
Promise.onPotentiallyUnhandledRejectionHandled(this.rejection);
}
};
// Unhandled rejection hooks
// By default, everything is a noop
Promise.createContext
= Promise.enterContext
= Promise.exitContext
= Promise.onPotentiallyUnhandledRejection
= Promise.onPotentiallyUnhandledRejectionHandled
= Promise.onFatalRejection
= noop;
// Errors and singletons
var foreverPendingHandler = new Handler();
var foreverPendingPromise = new Promise(Handler, foreverPendingHandler);
function cycle() {
return new Rejected(new TypeError('Promise cycle'));
}
// Task runners
/**
* Run a single consumer
* @constructor
*/
function ContinuationTask(continuation, handler) {
this.continuation = continuation;
this.handler = handler;
}
ContinuationTask.prototype.run = function() {
this.handler.join().when(this.continuation);
};
/**
* Run a queue of progress handlers
* @constructor
*/
function ProgressTask(value, handler) {
this.handler = handler;
this.value = value;
}
ProgressTask.prototype.run = function() {
var q = this.handler.consumers;
if(q === void 0) {
return;
}
for (var c, i = 0; i < q.length; ++i) {
c = q[i];
runNotify(c.progress, this.value, this.handler, c.receiver, c.resolver);
}
};
/**
* Assimilate a thenable, sending it's value to resolver
* @param {function} then
* @param {object|function} thenable
* @param {object} resolver
* @constructor
*/
function AssimilateTask(then, thenable, resolver) {
this._then = then;
this.thenable = thenable;
this.resolver = resolver;
}
AssimilateTask.prototype.run = function() {
var h = this.resolver;
tryAssimilate(this._then, this.thenable, _resolve, _reject, _notify);
function _resolve(x) { h.resolve(x); }
function _reject(x) { h.reject(x); }
function _notify(x) { h.notify(x); }
};
function tryAssimilate(then, thenable, resolve, reject, notify) {
try {
then.call(thenable, resolve, reject, notify);
} catch (e) {
reject(e);
}
}
/**
* Fold a handler value with z
* @constructor
*/
function Fold(f, z, c, to) {
this.f = f; this.z = z; this.c = c; this.to = to;
this.resolver = failIfRejected;
this.receiver = this;
}
Fold.prototype.fulfilled = function(x) {
this.f.call(this.c, this.z, x, this.to);
};
Fold.prototype.rejected = function(x) {
this.to.reject(x);
};
Fold.prototype.progress = function(x) {
this.to.notify(x);
};
// Other helpers
/**
* @param {*} x
* @returns {boolean} true iff x is a trusted Promise
*/
function isPromise(x) {
return x instanceof Promise;
}
/**
* Test just enough to rule out primitives, in order to take faster
* paths in some code
* @param {*} x
* @returns {boolean} false iff x is guaranteed *not* to be a thenable
*/
function maybeThenable(x) {
return (typeof x === 'object' || typeof x === 'function') && x !== null;
}
function runContinuation1(f, h, receiver, next) {
if(typeof f !== 'function') {
return next.become(h);
}
Promise.enterContext(h);
tryCatchReject(f, h.value, receiver, next);
Promise.exitContext();
}
function runContinuation3(f, x, h, receiver, next) {
if(typeof f !== 'function') {
return next.become(h);
}
Promise.enterContext(h);
tryCatchReject3(f, x, h.value, receiver, next);
Promise.exitContext();
}
/**
* @deprecated
*/
function runNotify(f, x, h, receiver, next) {
if(typeof f !== 'function') {
return next.notify(x);
}
Promise.enterContext(h);
tryCatchReturn(f, x, receiver, next);
Promise.exitContext();
}
function tryCatch2(f, a, b) {
try {
return f(a, b);
} catch(e) {
return reject(e);
}
}
/**
* Return f.call(thisArg, x), or if it throws return a rejected promise for
* the thrown exception
*/
function tryCatchReject(f, x, thisArg, next) {
try {
next.become(getHandler(f.call(thisArg, x)));
} catch(e) {
next.become(new Rejected(e));
}
}
/**
* Same as above, but includes the extra argument parameter.
*/
function tryCatchReject3(f, x, y, thisArg, next) {
try {
f.call(thisArg, x, y, next);
} catch(e) {
next.become(new Rejected(e));
}
}
/**
* @deprecated
* Return f.call(thisArg, x), or if it throws, *return* the exception
*/
function tryCatchReturn(f, x, thisArg, next) {
try {
next.notify(f.call(thisArg, x));
} catch(e) {
next.notify(e);
}
}
function inherit(Parent, Child) {
Child.prototype = objectCreate(Parent.prototype);
Child.prototype.constructor = Child;
}
function snd(x, y) {
return y;
}
function noop() {}
function initEmitRejection() {
/*global process, self, CustomEvent*/
if(typeof process !== 'undefined' && process !== null
&& typeof process.emit === 'function') {
// Returning falsy here means to call the default
// onPotentiallyUnhandledRejection API. This is safe even in
// browserify since process.emit always returns falsy in browserify:
// https://github.com/defunctzombie/node-process/blob/master/browser.js#L40-L46
return function(type, rejection) {
return type === 'unhandledRejection'
? process.emit(type, rejection.value, rejection)
: process.emit(type, rejection);
};
} else if(typeof self !== 'undefined' && typeof CustomEvent === 'function') {
return (function(noop, self, CustomEvent) {
var hasCustomEvent = false;
try {
var ev = new CustomEvent('unhandledRejection');
hasCustomEvent = ev instanceof CustomEvent;
} catch (e) {}
return !hasCustomEvent ? noop : function(type, rejection) {
var ev = new CustomEvent(type, {
detail: {
reason: rejection.value,
key: rejection
},
bubbles: false,
cancelable: true
});
return !self.dispatchEvent(ev);
};
}(noop, self, CustomEvent));
}
return noop;
}
return Promise;
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,35 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
return {
pending: toPendingState,
fulfilled: toFulfilledState,
rejected: toRejectedState,
inspect: inspect
};
function toPendingState() {
return { state: 'pending' };
}
function toRejectedState(e) {
return { state: 'rejected', reason: e };
}
function toFulfilledState(x) {
return { state: 'fulfilled', value: x };
}
function inspect(handler) {
var state = handler.state();
return state === 0 ? toPendingState()
: state > 0 ? toFulfilledState(handler.value)
: toRejectedState(handler.value);
}
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,17 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var PromiseMonitor = require('./monitor/PromiseMonitor');
var ConsoleReporter = require('./monitor/ConsoleReporter');
var promiseMonitor = new PromiseMonitor(new ConsoleReporter());
return function(Promise) {
return promiseMonitor.monitor(Promise);
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,106 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var error = require('./error');
var unhandledRejectionsMsg = '[promises] Unhandled rejections: ';
var allHandledMsg = '[promises] All previously unhandled rejections have now been handled';
function ConsoleReporter() {
this._previouslyReported = false;
}
ConsoleReporter.prototype = initDefaultLogging();
ConsoleReporter.prototype.log = function(traces) {
if(traces.length === 0) {
if(this._previouslyReported) {
this._previouslyReported = false;
this.msg(allHandledMsg);
}
return;
}
this._previouslyReported = true;
this.groupStart(unhandledRejectionsMsg + traces.length);
try {
this._log(traces);
} finally {
this.groupEnd();
}
};
ConsoleReporter.prototype._log = function(traces) {
for(var i=0; i<traces.length; ++i) {
this.warn(error.format(traces[i]));
}
};
function initDefaultLogging() {
/*jshint maxcomplexity:7*/
var log, warn, groupStart, groupEnd;
if(typeof console === 'undefined') {
log = warn = consoleNotAvailable;
} else {
// Alias console to prevent things like uglify's drop_console option from
// removing console.log/error. Unhandled rejections fall into the same
// category as uncaught exceptions, and build tools shouldn't silence them.
var localConsole = console;
if(typeof localConsole.error === 'function'
&& typeof localConsole.dir === 'function') {
warn = function(s) {
localConsole.error(s);
};
log = function(s) {
localConsole.log(s);
};
if(typeof localConsole.groupCollapsed === 'function') {
groupStart = function(s) {
localConsole.groupCollapsed(s);
};
groupEnd = function() {
localConsole.groupEnd();
};
}
} else {
// IE8 has console.log and JSON, so we can make a
// reasonably useful warn() from those.
// Credit to webpro (https://github.com/webpro) for this idea
// typeof localConsole.log will return 'object' in IE8, so can't test it with === 'function'
// Since this is more of a corner case for IE8, I'm ok to check it with !== 'undefined' to reduce complexity
if (typeof localConsole.log !== 'undefined' && typeof JSON !== 'undefined') {
log = warn = function(x) {
if (typeof x !== 'string') {
try {
x = JSON.stringify(x);
} catch (e) {
}
}
localConsole.log(x);
};
} else {
log = warn = consoleNotAvailable;
}
}
}
return {
msg: log,
warn: warn,
groupStart: groupStart || warn,
groupEnd: groupEnd || consoleNotAvailable
};
}
function consoleNotAvailable() {}
return ConsoleReporter;
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,197 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var defaultStackJumpSeparator = 'from execution context:';
var defaultStackFilter = /[\s\(\/\\](node|module|timers)\.js:|when([\/\\]{1,2}(lib|monitor|es6-shim)[\/\\]{1,2}|\.js)|(new\sPromise)\b|(\b(PromiseMonitor|ConsoleReporter|Scheduler|RunHandlerTask|ProgressTask|Promise|.*Handler)\.[\w_]\w\w+\b)|\b(tryCatch\w+|getHandler\w*)\b/i;
var setTimer = require('../lib/env').setTimer;
var error = require('./error');
var executionContext = [];
function PromiseMonitor(reporter) {
this.logDelay = 0;
this.stackFilter = defaultStackFilter;
this.stackJumpSeparator = defaultStackJumpSeparator;
this.filterDuplicateFrames = true;
this._reporter = reporter;
if(typeof reporter.configurePromiseMonitor === 'function') {
reporter.configurePromiseMonitor(this);
}
this._traces = [];
this._traceTask = 0;
var self = this;
this._doLogTraces = function() {
self._logTraces();
};
}
PromiseMonitor.prototype.monitor = function(Promise) {
var self = this;
Promise.createContext = function(p, context) {
p.context = self.createContext(p, context);
};
Promise.enterContext = function(p) {
executionContext.push(p.context);
};
Promise.exitContext = function() {
executionContext.pop();
};
Promise.onPotentiallyUnhandledRejection = function(rejection, extraContext) {
return self.addTrace(rejection, extraContext);
};
Promise.onPotentiallyUnhandledRejectionHandled = function(rejection) {
return self.removeTrace(rejection);
};
Promise.onFatalRejection = function(rejection, extraContext) {
return self.fatal(rejection, extraContext);
};
return this;
};
PromiseMonitor.prototype.createContext = function(at, parentContext) {
var context = {
parent: parentContext || executionContext[executionContext.length - 1],
stack: void 0
};
error.captureStack(context, at.constructor);
return context;
};
PromiseMonitor.prototype.addTrace = function(handler, extraContext) {
var t, i;
for(i = this._traces.length-1; i >= 0; --i) {
t = this._traces[i];
if(t.handler === handler) {
break;
}
}
if(i >= 0) {
t.extraContext = extraContext;
} else {
this._traces.push({
handler: handler,
extraContext: extraContext
});
}
this.logTraces();
};
PromiseMonitor.prototype.removeTrace = function(/*handler*/) {
this.logTraces();
};
PromiseMonitor.prototype.fatal = function(handler, extraContext) {
var err = new Error();
err.stack = this._createLongTrace(handler.value, handler.context, extraContext).join('\n');
setTimer(function() {
throw err;
}, 0);
};
PromiseMonitor.prototype.logTraces = function() {
if(!this._traceTask) {
this._traceTask = setTimer(this._doLogTraces, this.logDelay);
}
};
PromiseMonitor.prototype._logTraces = function() {
this._traceTask = void 0;
this._traces = this._traces.filter(filterHandled);
this._reporter.log(this.formatTraces(this._traces));
};
PromiseMonitor.prototype.formatTraces = function(traces) {
return traces.map(function(t) {
return this._createLongTrace(t.handler.value, t.handler.context, t.extraContext);
}, this);
};
PromiseMonitor.prototype._createLongTrace = function(e, context, extraContext) {
var trace = error.parse(e) || [String(e) + ' (WARNING: non-Error used)'];
trace = filterFrames(this.stackFilter, trace, 0);
this._appendContext(trace, context);
this._appendContext(trace, extraContext);
return this.filterDuplicateFrames ? this._removeDuplicates(trace) : trace;
};
PromiseMonitor.prototype._removeDuplicates = function(trace) {
var seen = {};
var sep = this.stackJumpSeparator;
var count = 0;
return trace.reduceRight(function(deduped, line, i) {
if(i === 0) {
deduped.unshift(line);
} else if(line === sep) {
if(count > 0) {
deduped.unshift(line);
count = 0;
}
} else if(!seen[line]) {
seen[line] = true;
deduped.unshift(line);
++count;
}
return deduped;
}, []);
};
PromiseMonitor.prototype._appendContext = function(trace, context) {
trace.push.apply(trace, this._createTrace(context));
};
PromiseMonitor.prototype._createTrace = function(traceChain) {
var trace = [];
var stack;
while(traceChain) {
stack = error.parse(traceChain);
if (stack) {
stack = filterFrames(this.stackFilter, stack);
appendStack(trace, stack, this.stackJumpSeparator);
}
traceChain = traceChain.parent;
}
return trace;
};
function appendStack(trace, stack, separator) {
if (stack.length > 1) {
stack[0] = separator;
trace.push.apply(trace, stack);
}
}
function filterFrames(stackFilter, stack) {
return stack.filter(function(frame) {
return !stackFilter.test(frame);
});
}
function filterHandled(t) {
return !t.handler.handled;
}
return PromiseMonitor;
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,14 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var monitor = require('../monitor');
var Promise = require('../when').Promise;
return monitor(Promise);
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,86 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function() {
var parse, captureStack, format;
if(Error.captureStackTrace) {
// Use Error.captureStackTrace if available
parse = function(e) {
return e && e.stack && e.stack.split('\n');
};
format = formatAsString;
captureStack = Error.captureStackTrace;
} else {
// Otherwise, do minimal feature detection to determine
// how to capture and format reasonable stacks.
parse = function(e) {
var stack = e && e.stack && e.stack.split('\n');
if(stack && e.message) {
stack.unshift(e.message);
}
return stack;
};
(function() {
var e = new Error();
if(typeof e.stack !== 'string') {
format = formatAsString;
captureStack = captureSpiderMonkeyStack;
} else {
format = formatAsErrorWithStack;
captureStack = useStackDirectly;
}
}());
}
function captureSpiderMonkeyStack(host) {
try {
throw new Error();
} catch(err) {
host.stack = err.stack;
}
}
function useStackDirectly(host) {
host.stack = new Error().stack;
}
function formatAsString(longTrace) {
return join(longTrace);
}
function formatAsErrorWithStack(longTrace) {
var e = new Error();
e.stack = formatAsString(longTrace);
return e;
}
// About 5-10x faster than String.prototype.join o_O
function join(a) {
var sep = false;
var s = '';
for(var i=0; i< a.length; ++i) {
if(sep) {
s += '\n' + a[i];
} else {
s+= a[i];
sep = true;
}
}
return s;
}
return {
parse: parse,
format: format,
captureStack: captureStack
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));

View File

@ -0,0 +1,282 @@
/** @license MIT License (c) copyright 2013 original author or authors */
/**
* Collection of helpers for interfacing with node-style asynchronous functions
* using promises.
*
* @author Brian Cavalier
* @contributor Renato Zannon
*/
(function(define) {
define(function(require) {
var when = require('./when');
var _liftAll = require('./lib/liftAll');
var setTimer = require('./lib/env').setTimer;
var slice = Array.prototype.slice;
var _apply = require('./lib/apply')(when.Promise, dispatch);
return {
lift: lift,
liftAll: liftAll,
apply: apply,
call: call,
createCallback: createCallback,
bindCallback: bindCallback,
liftCallback: liftCallback
};
/**
* Takes a node-style async function and calls it immediately (with an optional
* array of arguments or promises for arguments). It returns a promise whose
* resolution depends on whether the async functions calls its callback with the
* conventional error argument or not.
*
* With this it becomes possible to leverage existing APIs while still reaping
* the benefits of promises.
*
* @example
* function onlySmallNumbers(n, callback) {
* if(n < 10) {
* callback(null, n + 10);
* } else {
* callback(new Error("Calculation failed"));
* }
* }
*
* var nodefn = require("when/node/function");
*
* // Logs '15'
* nodefn.apply(onlySmallNumbers, [5]).then(console.log, console.error);
*
* // Logs 'Calculation failed'
* nodefn.apply(onlySmallNumbers, [15]).then(console.log, console.error);
*
* @param {function} f node-style function that will be called
* @param {Array} [args] array of arguments to func
* @returns {Promise} promise for the value func passes to its callback
*/
function apply(f, args) {
return _apply(f, this, args || []);
}
function dispatch(f, thisArg, args, h) {
var cb = createCallback(h);
try {
switch(args.length) {
case 2: f.call(thisArg, args[0], args[1], cb); break;
case 1: f.call(thisArg, args[0], cb); break;
case 0: f.call(thisArg, cb); break;
default:
args.push(cb);
f.apply(thisArg, args);
}
} catch(e) {
h.reject(e);
}
}
/**
* Has the same behavior that {@link apply} has, with the difference that the
* arguments to the function are provided individually, while {@link apply} accepts
* a single array.
*
* @example
* function sumSmallNumbers(x, y, callback) {
* var result = x + y;
* if(result < 10) {
* callback(null, result);
* } else {
* callback(new Error("Calculation failed"));
* }
* }
*
* // Logs '5'
* nodefn.call(sumSmallNumbers, 2, 3).then(console.log, console.error);
*
* // Logs 'Calculation failed'
* nodefn.call(sumSmallNumbers, 5, 10).then(console.log, console.error);
*
* @param {function} f node-style function that will be called
* @param {...*} [args] arguments that will be forwarded to the function
* @returns {Promise} promise for the value func passes to its callback
*/
function call(f /*, args... */) {
return _apply(f, this, slice.call(arguments, 1));
}
/**
* Takes a node-style function and returns new function that wraps the
* original and, instead of taking a callback, returns a promise. Also, it
* knows how to handle promises given as arguments, waiting for their
* resolution before executing.
*
* Upon execution, the orginal function is executed as well. If it passes
* a truthy value as the first argument to the callback, it will be
* interpreted as an error condition, and the promise will be rejected
* with it. Otherwise, the call is considered a resolution, and the promise
* is resolved with the callback's second argument.
*
* @example
* var fs = require("fs"), nodefn = require("when/node/function");
*
* var promiseRead = nodefn.lift(fs.readFile);
*
* // The promise is resolved with the contents of the file if everything
* // goes ok
* promiseRead('exists.txt').then(console.log, console.error);
*
* // And will be rejected if something doesn't work out
* // (e.g. the files does not exist)
* promiseRead('doesnt_exist.txt').then(console.log, console.error);
*
*
* @param {Function} f node-style function to be lifted
* @param {...*} [args] arguments to be prepended for the new function @deprecated
* @returns {Function} a promise-returning function
*/
function lift(f /*, args... */) {
var args1 = arguments.length > 1 ? slice.call(arguments, 1) : [];
return function() {
// TODO: Simplify once partialing has been removed
var l = args1.length;
var al = arguments.length;
var args = new Array(al + l);
var i;
for(i=0; i<l; ++i) {
args[i] = args1[i];
}
for(i=0; i<al; ++i) {
args[i+l] = arguments[i];
}
return _apply(f, this, args);
};
}
/**
* Lift all the functions/methods on src
* @param {object|function} src source whose functions will be lifted
* @param {function?} combine optional function for customizing the lifting
* process. It is passed dst, the lifted function, and the property name of
* the original function on src.
* @param {(object|function)?} dst option destination host onto which to place lifted
* functions. If not provided, liftAll returns a new object.
* @returns {*} If dst is provided, returns dst with lifted functions as
* properties. If dst not provided, returns a new object with lifted functions.
*/
function liftAll(src, combine, dst) {
return _liftAll(lift, combine, dst, src);
}
/**
* Takes an object that responds to the resolver interface, and returns
* a function that will resolve or reject it depending on how it is called.
*
* @example
* function callbackTakingFunction(callback) {
* if(somethingWrongHappened) {
* callback(error);
* } else {
* callback(null, interestingValue);
* }
* }
*
* var when = require('when'), nodefn = require('when/node/function');
*
* var deferred = when.defer();
* callbackTakingFunction(nodefn.createCallback(deferred.resolver));
*
* deferred.promise.then(function(interestingValue) {
* // Use interestingValue
* });
*
* @param {Resolver} resolver that will be 'attached' to the callback
* @returns {Function} a node-style callback function
*/
function createCallback(resolver) {
return function(err, value) {
if(err) {
resolver.reject(err);
} else if(arguments.length > 2) {
resolver.resolve(slice.call(arguments, 1));
} else {
resolver.resolve(value);
}
};
}
/**
* Attaches a node-style callback to a promise, ensuring the callback is
* called for either fulfillment or rejection. Returns a promise with the same
* state as the passed-in promise.
*
* @example
* var deferred = when.defer();
*
* function callback(err, value) {
* // Handle err or use value
* }
*
* bindCallback(deferred.promise, callback);
*
* deferred.resolve('interesting value');
*
* @param {Promise} promise The promise to be attached to.
* @param {Function} callback The node-style callback to attach.
* @returns {Promise} A promise with the same state as the passed-in promise.
*/
function bindCallback(promise, callback) {
promise = when(promise);
if (callback) {
promise.then(success, wrapped);
}
return promise;
function success(value) {
wrapped(null, value);
}
function wrapped(err, value) {
setTimer(function () {
callback(err, value);
}, 0);
}
}
/**
* Takes a node-style callback and returns new function that accepts a
* promise, calling the original callback when the promise is either
* fulfilled or rejected with the appropriate arguments.
*
* @example
* var deferred = when.defer();
*
* function callback(err, value) {
* // Handle err or use value
* }
*
* var wrapped = liftCallback(callback);
*
* // `wrapped` can now be passed around at will
* wrapped(deferred.promise);
*
* deferred.resolve('interesting value');
*
* @param {Function} callback The node-style callback to wrap.
* @returns {Function} The lifted, promise-accepting function.
*/
function liftCallback(callback) {
return function(promise) {
return bindCallback(promise, callback);
};
}
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,13 @@
/** @license MIT License (c) copyright 2013 original author or authors */
/**
* @author Brian Cavalier
*/
(function(define) { 'use strict';
define(function(require) {
// DEPRECATED: Use when/node instead
return require('../node');
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

View File

@ -0,0 +1,87 @@
{
"name": "when",
"version": "3.7.3",
"description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.",
"keywords": [
"cujo",
"Promises/A+",
"promises-aplus",
"promise",
"promises",
"deferred",
"deferreds",
"when",
"async",
"asynchronous",
"ender"
],
"homepage": "http://cujojs.com",
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"repository": {
"type": "git",
"url": "https://github.com/cujojs/when"
},
"bugs": "https://github.com/cujojs/when/issues",
"maintainers": [
{
"name": "Brian Cavalier",
"web": "http://hovercraftstudios.com"
},
{
"name": "John Hann",
"web": "http://unscriptable.com"
}
],
"contributors": [
{
"name": "Brian Cavalier",
"web": "http://hovercraftstudios.com"
},
{
"name": "John Hann",
"web": "http://unscriptable.com"
},
{
"name": "Scott Andrews"
}
],
"devDependencies": {
"promises-aplus-tests": "~2",
"benchmark": "~1",
"microtime": "~0",
"browserify": "~2",
"buster": "~0.7",
"jshint": "~2",
"rest": "1.1.x",
"optimist": "~0.6",
"sauce-connect-launcher": "~0.4",
"wd": "~0.2",
"json5": "~0.2",
"poly": "git://github.com/cujojs/poly#0.6.0"
},
"main": "when",
"ender": { "files": ["*.js", "lib/*.js", "node/*.js", "unfold/*.js", "monitor/*.js"] },
"browser": {
"vertx": false
},
"directories": {
"test": "test"
},
"scripts": {
"test": "jshint . && buster-test -e node && promises-aplus-tests test/promises-aplus-adapter.js",
"build-browser-test": "browserify ./node_modules/poly/es5.js -o test/browser/es5.js && browserify ./test/*-test.js ./test/**/*-test.js -o test/browser/tests.js -x buster ",
"browser-test": "npm run build-browser-test && buster-static -e browser -p 8080",
"ci": "npm test && node test/sauce.js",
"tunnel": "node test/sauce.js -m",
"start": "buster-static -e browser",
"benchmark": "node benchmark/promise && node benchmark/map",
"browserify-es6": "browserify -s Promise es6-shim/Promise.browserify-es6.js --no-detect-globals -o es6-shim/Promise.js",
"browserify": "browserify -s when build/when.browserify.js --no-detect-globals -o build/when.js",
"browserify-debug": "browserify -s when build/when.browserify-debug.js --no-detect-globals -o build/when.js"
}
}

View File

@ -0,0 +1,39 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* parallel.js
*
* Run a set of task functions in parallel. All tasks will
* receive the same args
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) {
define(function(require) {
var when = require('./when');
var all = when.Promise.all;
var slice = Array.prototype.slice;
/**
* Run array of tasks in parallel
* @param tasks {Array|Promise} array or promiseForArray of task functions
* @param [args] {*} arguments to be passed to all tasks
* @return {Promise} promise for array containing the
* result of each task in the array position corresponding
* to position of the task in the tasks array
*/
return function parallel(tasks /*, args... */) {
return all(slice.call(arguments, 1)).then(function(args) {
return when.map(tasks, function(task) {
return task.apply(void 0, args);
});
});
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,50 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* pipeline.js
*
* Run a set of task functions in sequence, passing the result
* of the previous as an argument to the next. Like a shell
* pipeline, e.g. `cat file.txt | grep 'foo' | sed -e 's/foo/bar/g'
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) {
define(function(require) {
var when = require('./when');
var all = when.Promise.all;
var slice = Array.prototype.slice;
/**
* Run array of tasks in a pipeline where the next
* tasks receives the result of the previous. The first task
* will receive the initialArgs as its argument list.
* @param tasks {Array|Promise} array or promise for array of task functions
* @param [initialArgs...] {*} arguments to be passed to the first task
* @return {Promise} promise for return value of the final task
*/
return function pipeline(tasks /* initialArgs... */) {
// Self-optimizing function to run first task with multiple
// args using apply, but subsequence tasks via direct invocation
var runTask = function(args, task) {
runTask = function(arg, task) {
return task(arg);
};
return task.apply(null, args);
};
return all(slice.call(arguments, 1)).then(function(args) {
return when.reduce(tasks, function(arg, task) {
return runTask(arg, task);
}, args);
});
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,114 @@
/** @license MIT License (c) copyright 2012-2013 original author or authors */
/**
* poll.js
*
* Helper that polls until cancelled or for a condition to become true.
*
* @author Scott Andrews
*/
(function (define) { 'use strict';
define(function(require) {
var when = require('./when');
var attempt = when['try'];
var cancelable = require('./cancelable');
/**
* Periodically execute the task function on the msec delay. The result of
* the task may be verified by watching for a condition to become true. The
* returned deferred is cancellable if the polling needs to be cancelled
* externally before reaching a resolved state.
*
* The next vote is scheduled after the results of the current vote are
* verified and rejected.
*
* Polling may be terminated by the verifier returning a truthy value,
* invoking cancel() on the returned promise, or the task function returning
* a rejected promise.
*
* Usage:
*
* var count = 0;
* function doSomething() { return count++ }
*
* // poll until cancelled
* var p = poll(doSomething, 1000);
* ...
* p.cancel();
*
* // poll until condition is met
* poll(doSomething, 1000, function(result) { return result > 10 })
* .then(function(result) { assert result == 10 });
*
* // delay first vote
* poll(doSomething, 1000, anyFunc, true);
*
* @param task {Function} function that is executed after every timeout
* @param interval {number|Function} timeout in milliseconds
* @param [verifier] {Function} function to evaluate the result of the vote.
* May return a {Promise} or a {Boolean}. Rejecting the promise or a
* falsey value will schedule the next vote.
* @param [delayInitialTask] {boolean} if truthy, the first vote is scheduled
* instead of immediate
*
* @returns {Promise}
*/
return function poll(task, interval, verifier, delayInitialTask) {
var deferred, canceled, reject;
canceled = false;
deferred = cancelable(when.defer(), function () { canceled = true; });
reject = deferred.reject;
verifier = verifier || function () { return false; };
if (typeof interval !== 'function') {
interval = (function (interval) {
return function () { return when().delay(interval); };
})(interval);
}
function certify(result) {
deferred.resolve(result);
}
function schedule(result) {
attempt(interval).then(vote, reject);
if (result !== void 0) {
deferred.notify(result);
}
}
function vote() {
if (canceled) { return; }
when(task(),
function (result) {
when(verifier(result),
function (verification) {
return verification ? certify(result) : schedule(result);
},
function () { schedule(result); }
);
},
reject
);
}
if (delayInitialTask) {
schedule();
} else {
// if task() is blocking, vote will also block
vote();
}
// make the promise cancelable
deferred.promise = Object.create(deferred.promise);
deferred.promise.cancel = deferred.cancel;
return deferred.promise;
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,46 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* sequence.js
*
* Run a set of task functions in sequence. All tasks will
* receive the same args.
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) {
define(function(require) {
var when = require('./when');
var all = when.Promise.all;
var slice = Array.prototype.slice;
/**
* Run array of tasks in sequence with no overlap
* @param tasks {Array|Promise} array or promiseForArray of task functions
* @param [args] {*} arguments to be passed to all tasks
* @return {Promise} promise for an array containing
* the result of each task in the array position corresponding
* to position of the task in the tasks array
*/
return function sequence(tasks /*, args... */) {
var results = [];
return all(slice.call(arguments, 1)).then(function(args) {
return when.reduce(tasks, function(results, task) {
return when(task.apply(void 0, args), addResult);
}, results);
});
function addResult(result) {
results.push(result);
return results;
}
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,27 @@
/** @license MIT License (c) copyright 2011-2013 original author or authors */
/**
* timeout.js
*
* Helper that returns a promise that rejects after a specified timeout,
* if not explicitly resolved or rejected before that.
*
* @author Brian Cavalier
* @author John Hann
*/
(function(define) {
define(function(require) {
var when = require('./when');
/**
* @deprecated Use when(trigger).timeout(ms)
*/
return function timeout(msec, trigger) {
return when(trigger).timeout(msec);
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,17 @@
/** @license MIT License (c) copyright B Cavalier & J Hann */
/**
* unfold
* @author: brian@hovercraftstudios.com
*/
(function(define) {
define(function(require) {
/**
* @deprecated Use when.unfold
*/
return require('./when').unfold;
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } );

View File

@ -0,0 +1,32 @@
/** @license MIT License (c) copyright B Cavalier & J Hann */
(function(define) {
define(function(require) {
var unfold = require('../when').unfold;
/**
* @deprecated
* Given a seed and generator, produces an Array. Effectively the
* dual (opposite) of when.reduce()
* @param {function} generator function that generates a value (or promise
* for a value) to be placed in the resulting array
* @param {function} condition given a seed, must return truthy if the unfold
* should continue, or falsey if it should terminate
* @param {*|Promise} seed any value or promise
* @return {Promise} resulting array
*/
return function list(generator, condition, seed) {
var result = [];
return unfold(generator, condition, append, seed)['yield'](result);
function append(value, newSeed) {
result.push(value);
return newSeed;
}
};
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,228 @@
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/**
* Promises/A+ and when() implementation
* when is part of the cujoJS family of libraries (http://cujojs.com/)
* @author Brian Cavalier
* @author John Hann
*/
(function(define) { 'use strict';
define(function (require) {
var timed = require('./lib/decorators/timed');
var array = require('./lib/decorators/array');
var flow = require('./lib/decorators/flow');
var fold = require('./lib/decorators/fold');
var inspect = require('./lib/decorators/inspect');
var generate = require('./lib/decorators/iterate');
var progress = require('./lib/decorators/progress');
var withThis = require('./lib/decorators/with');
var unhandledRejection = require('./lib/decorators/unhandledRejection');
var TimeoutError = require('./lib/TimeoutError');
var Promise = [array, flow, fold, generate, progress,
inspect, withThis, timed, unhandledRejection]
.reduce(function(Promise, feature) {
return feature(Promise);
}, require('./lib/Promise'));
var apply = require('./lib/apply')(Promise);
// Public API
when.promise = promise; // Create a pending promise
when.resolve = Promise.resolve; // Create a resolved promise
when.reject = Promise.reject; // Create a rejected promise
when.lift = lift; // lift a function to return promises
when['try'] = attempt; // call a function and return a promise
when.attempt = attempt; // alias for when.try
when.iterate = Promise.iterate; // DEPRECATED (use cujojs/most streams) Generate a stream of promises
when.unfold = Promise.unfold; // DEPRECATED (use cujojs/most streams) Generate a stream of promises
when.join = join; // Join 2 or more promises
when.all = all; // Resolve a list of promises
when.settle = settle; // Settle a list of promises
when.any = lift(Promise.any); // One-winner race
when.some = lift(Promise.some); // Multi-winner race
when.race = lift(Promise.race); // First-to-settle race
when.map = map; // Array.map() for promises
when.filter = filter; // Array.filter() for promises
when.reduce = lift(Promise.reduce); // Array.reduce() for promises
when.reduceRight = lift(Promise.reduceRight); // Array.reduceRight() for promises
when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable
when.Promise = Promise; // Promise constructor
when.defer = defer; // Create a {promise, resolve, reject} tuple
// Error types
when.TimeoutError = TimeoutError;
/**
* Get a trusted promise for x, or by transforming x with onFulfilled
*
* @param {*} x
* @param {function?} onFulfilled callback to be called when x is
* successfully fulfilled. If promiseOrValue is an immediate value, callback
* will be invoked immediately.
* @param {function?} onRejected callback to be called when x is
* rejected.
* @param {function?} onProgress callback to be called when progress updates
* are issued for x. @deprecated
* @returns {Promise} a new promise that will fulfill with the return
* value of callback or errback or the completion value of promiseOrValue if
* callback and/or errback is not supplied.
*/
function when(x, onFulfilled, onRejected, onProgress) {
var p = Promise.resolve(x);
if (arguments.length < 2) {
return p;
}
return p.then(onFulfilled, onRejected, onProgress);
}
/**
* Creates a new promise whose fate is determined by resolver.
* @param {function} resolver function(resolve, reject, notify)
* @returns {Promise} promise whose fate is determine by resolver
*/
function promise(resolver) {
return new Promise(resolver);
}
/**
* Lift the supplied function, creating a version of f that returns
* promises, and accepts promises as arguments.
* @param {function} f
* @returns {Function} version of f that returns promises
*/
function lift(f) {
return function() {
for(var i=0, l=arguments.length, a=new Array(l); i<l; ++i) {
a[i] = arguments[i];
}
return apply(f, this, a);
};
}
/**
* Call f in a future turn, with the supplied args, and return a promise
* for the result.
* @param {function} f
* @returns {Promise}
*/
function attempt(f /*, args... */) {
/*jshint validthis:true */
for(var i=0, l=arguments.length-1, a=new Array(l); i<l; ++i) {
a[i] = arguments[i+1];
}
return apply(f, this, a);
}
/**
* Creates a {promise, resolver} pair, either or both of which
* may be given out safely to consumers.
* @return {{promise: Promise, resolve: function, reject: function, notify: function}}
*/
function defer() {
return new Deferred();
}
function Deferred() {
var p = Promise._defer();
function resolve(x) { p._handler.resolve(x); }
function reject(x) { p._handler.reject(x); }
function notify(x) { p._handler.notify(x); }
this.promise = p;
this.resolve = resolve;
this.reject = reject;
this.notify = notify;
this.resolver = { resolve: resolve, reject: reject, notify: notify };
}
/**
* Determines if x is promise-like, i.e. a thenable object
* NOTE: Will return true for *any thenable object*, and isn't truly
* safe, since it may attempt to access the `then` property of x (i.e.
* clever/malicious getters may do weird things)
* @param {*} x anything
* @returns {boolean} true if x is promise-like
*/
function isPromiseLike(x) {
return x && typeof x.then === 'function';
}
/**
* Return a promise that will resolve only once all the supplied arguments
* have resolved. The resolution value of the returned promise will be an array
* containing the resolution values of each of the arguments.
* @param {...*} arguments may be a mix of promises and values
* @returns {Promise}
*/
function join(/* ...promises */) {
return Promise.all(arguments);
}
/**
* Return a promise that will fulfill once all input promises have
* fulfilled, or reject when any one input promise rejects.
* @param {array|Promise} promises array (or promise for an array) of promises
* @returns {Promise}
*/
function all(promises) {
return when(promises, Promise.all);
}
/**
* Return a promise that will always fulfill with an array containing
* the outcome states of all input promises. The returned promise
* will only reject if `promises` itself is a rejected promise.
* @param {array|Promise} promises array (or promise for an array) of promises
* @returns {Promise} promise for array of settled state descriptors
*/
function settle(promises) {
return when(promises, Promise.settle);
}
/**
* Promise-aware array map function, similar to `Array.prototype.map()`,
* but input array may contain promises or values.
* @param {Array|Promise} promises array of anything, may contain promises and values
* @param {function(x:*, index:Number):*} mapFunc map function which may
* return a promise or value
* @returns {Promise} promise that will fulfill with an array of mapped values
* or reject if any input promise rejects.
*/
function map(promises, mapFunc) {
return when(promises, function(promises) {
return Promise.map(promises, mapFunc);
});
}
/**
* Filter the provided array of promises using the provided predicate. Input may
* contain promises and values
* @param {Array|Promise} promises array of promises and values
* @param {function(x:*, index:Number):boolean} predicate filtering predicate.
* Must return truthy (or promise for truthy) for items to retain.
* @returns {Promise} promise that will fulfill with an array containing all items
* for which predicate returned truthy.
*/
function filter(promises, predicate) {
return when(promises, function(promises) {
return Promise.filter(promises, predicate);
});
}
return when;
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });

View File

@ -0,0 +1,3 @@
define(['bower/when/when'], function (when) {
return when;
});

View File

@ -1,20 +0,0 @@
define [
'react'
'jsx/assignments/ModeratedStudentList'
'jsx/assignments/stores/ModerationStore'
'jsx/assignments/actions/ModerationActions'
], (React, ModeratedStudentList, Store, Actions) ->
TestUtils = React.addons.TestUtils
module 'ModeratedStudentList',
test "renders mark 1", ->
store = new Store()
score = 10
moderatedStudentList = TestUtils.renderIntoDocument(ModeratedStudentList(store: store))
store.addSubmissions([{id: 1, user: {display_name: 'steve'}, provisional_grades: [{score:score}]}])
firstMark = TestUtils.scryRenderedDOMComponentsWithClass(moderatedStudentList, 'AssignmentList__Mark')[0].getDOMNode().textContent
equal firstMark, score, "renders the first mark"
React.unmountComponentAtNode(moderatedStudentList.getDOMNode().parentNode)

View File

@ -1,29 +1,157 @@
define [ define [
"jsx/assignments/actions/ModerationActions" "when"
], (ModerationActions) -> "jsx/assignments/actions/ModerationActions",
module "ModerationActions", ], (whenJS, ModerationActions) ->
test "sets this.store in the constructor", ->
some_store = {data: true}
actions = new ModerationActions(some_store)
ok actions.store.data, "sets the store to true"
#module "ModerationActions#loadInitalSubmissions", module "ModerationActions - Action Creators",
#setup: ->
#@server = sinon.fakeServer.create()
#teardown: ->
#@server.restore()
#test "fetches data from the submissions_url and sets store.addSubmissions with it", -> test "creates the SELECT_STUDENT action", ->
#the_data = {} action = ModerationActions.selectStudent(1)
#actions = new ModerationActions(some_store) expected =
#submission_url = "/something" type: ModerationActions.SELECT_STUDENT
#actions.loadInitialSubmissions(submission_url) payload:
#expected = {some_thing: 'here'} studentId: 1
#@server.respond 'get', submission_url, [
#200 deepEqual action, expected, "creates the action successfully"
#'Content-Type': 'application/json'
#JSON.stringify expected test "creates the GOT_STUDENTS action", ->
#] action = ModerationActions.gotStudents([1, 2, 3])
expected =
type: ModerationActions.GOT_STUDENTS
payload:
students: [1, 2, 3]
deepEqual action, expected, "creates the action successfully"
test "creates the PUBLISHED_GRADES action", ->
action = ModerationActions.publishedGrades('test')
expected =
type: ModerationActions.PUBLISHED_GRADES
payload:
message: 'test'
time: Date.now()
equal action.type, expected.type, "type matches"
equal action.payload.message, expected.payload.message, "message matches"
ok expected.payload.time - action.payload.time < 5, "time within 5 seconds"
test "creates the PUBLISHED_GRADES_FAILED action", ->
action = ModerationActions.publishGradesFailed('test')
expected =
type: ModerationActions.PUBLISHED_GRADES_FAILED
payload:
message: 'test'
time: Date.now()
error: true
equal action.type, expected.type, "type matches"
equal action.payload.message, expected.payload.message, "message matches"
ok action.error, "error flag is set"
ok expected.payload.time - action.payload.time < 5, "time within 5 seconds"
#equal the_data, expected, "gets data from the url" module "ModerationActions#apiGetStudents",
setup: ->
@client = {
get: ->
dfd = whenJS.defer()
setTimeout ->
dfd.resolve('test')
, 100
dfd.promise()
}
test "returns a function", ->
ok typeof ModerationActions.apiGetStudents() == 'function'
asyncTest "dispatches gotStudents action", ->
getState = ->
urls:
list_gradeable_students: 'some_url'
students: []
fakeResponse = {data: ['test']}
gotStudentsAction =
type: ModerationActions.GOT_STUDENTS
payload:
students: ['test']
sinon.stub(@client, 'get').returns(whenJS(fakeResponse))
ModerationActions.apiGetStudents(@client)((action) ->
deepEqual action, gotStudentsAction
start()
, getState)
module "ModerationActions#publishGrades",
setup: ->
@client = {
post: ->
dfd = whenJS.defer()
setTimeout ->
dfd.resolve('test')
, 100
dfd.promise()
}
test "returns a function", ->
ok typeof ModerationActions.publishGrades() == 'function'
asyncTest "dispatches publishGrades action on success", ->
getState = ->
urls:
publish_grades_url: 'some_url'
fakeResponse = {status: 200}
publishGradesAction =
type: ModerationActions.PUBLISHED_GRADES
payload:
message: 'Success! Grades were published to the grade book.'
sinon.stub(@client, 'post').returns(whenJS(fakeResponse))
ModerationActions.publishGrades(@client)((action) ->
equal action.type, publishGradesAction.type, 'type matches'
equal action.payload.message, publishGradesAction.payload.message, 'has proper message'
start()
, getState)
asyncTest "dispatches publishGradesFailed action with already published message on 400 failure", ->
getState = ->
urls:
publish_grades_url: 'some_url'
fakeResponse =
status: 400
publishGradesAction =
type: ModerationActions.PUBLISHED_GRADES_FAILED
payload:
message: 'Assignment grades have already been published.'
sinon.stub(@client, 'post').returns(whenJS.reject(fakeResponse))
ModerationActions.publishGrades(@client)((action) ->
equal action.type, publishGradesAction.type, 'type matches'
equal action.payload.message, publishGradesAction.payload.message, 'has proper message'
start()
, getState)
asyncTest "dispatches publishGradesFailed action with generic error message on non-400 error", ->
getState = ->
urls:
publish_grades_url: 'some_url'
fakeResponse =
status: 500
publishGradesAction =
type: ModerationActions.PUBLISHED_GRADES_FAILED
payload:
message: 'An error occurred publishing grades.'
sinon.stub(@client, 'post').returns(whenJS.reject(fakeResponse))
ModerationActions.publishGrades(@client)((action) ->
equal action.type, publishGradesAction.type, 'type matches'
equal action.payload.message, publishGradesAction.payload.message, 'has proper message'
start()
, getState)

View File

@ -0,0 +1,75 @@
define [
"jsx/assignments/reducers/rootReducer"
], (rootReducer) ->
module "students reducer",
test "concatenates students handling GOT_STUDENTS", ->
initialState =
students: ['one', 'two']
gotStudentsAction =
type: 'GOT_STUDENTS'
payload:
students: ['three', 'four']
newState = rootReducer(initialState, gotStudentsAction)
expected = ['one', 'two', 'three', 'four']
deepEqual newState.students, expected, 'successfully concatenates'
module "urls reducer",
test "passes through whatever the current state is", ->
initialState =
urls:
test_url: 'test'
someRandomAction =
type: 'Random'
newState = rootReducer(initialState, someRandomAction)
deepEqual newState.urls, initialState.urls, 'passes through unchanged'
module "assignments reducer",
test "sets to published on PUBLISHED_GRADES", ->
initialState =
assignments:
published: false
publishedGradesAction =
type: 'PUBLISHED_GRADES'
payload:
time: Date.now()
message: 'test'
newState = rootReducer(initialState, publishedGradesAction)
ok newState.assignment.published, 'successfully sets to publish'
module "flashMessage reducer",
test "sets success message on PUBLISHED_GRADES", ->
initialState =
flashMessage: {}
publishedGradesAction =
type: 'PUBLISHED_GRADES'
payload:
time: 123
message: 'test success'
newState = rootReducer(initialState, publishedGradesAction)
expected =
time: 123
message: 'test success'
error: false
deepEqual newState.flashMessage, expected, 'updates state'
test "sets failure message on PUBLISHED_GRADES_FAILED", ->
initialState =
flashMessage: {}
publishedGradesAction =
type: 'PUBLISHED_GRADES_FAILED'
payload:
time: 123
message: 'failed to publish'
error: true
newState = rootReducer(initialState, publishedGradesAction)
expected =
time: 123
message: 'failed to publish'
error: true
deepEqual newState.flashMessage, expected, 'updates state'

View File

@ -1,27 +0,0 @@
define ['jsx/assignments/stores/ModerationStore'], (ModerationStore) ->
module 'ModerationStore',
test 'constructor', ->
store = new ModerationStore()
ok store, "constructs properly"
equal store.submissions.length, 0, 'student list is initally empty'
test 'adds multiple submissions to the store', ->
store = new ModerationStore()
store.addSubmissions([{id: 1}, {id: 2}])
equal store.submissions.length, 2, 'store length is two'
test 'doesn\'t add duplicates to the store', ->
store = new ModerationStore()
store.addSubmissions([{id: 1}])
store.addSubmissions([{id: 1}])
equal store.submissions.length, 1, 'store length is one'
test 'triggers change when adding submissions', ->
store = new ModerationStore()
called = false
store.addChangeListener () ->
called = true
store.addSubmissions([{id: 1}])
ok called, 'change listener handler was called'