Merge pull request #853 from isiahmeadows/prop-fix

Isolate m.prop() and m.deferred() implementations (mostly)
This commit is contained in:
Isiah Meadows 2015-12-15 06:41:09 -05:00
commit e63a6f37bd
5 changed files with 176 additions and 131 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
node_modules node_modules
archive

View file

@ -1201,9 +1201,52 @@ void (function (global, factory) { // eslint-disable-line
return prop return prop
} }
function isPromise(object) {
return object != null && (isObject(object) || isFunction(object)) &&
isFunction(object.then)
}
function simpleResolve(p, callback) {
if (p.then) {
return p.then(callback)
} else {
return callback()
}
}
function propify(promise) {
var prop = m.prop()
promise.then(prop)
prop.then = function (resolve, reject) {
return promise.then(function () {
return resolve(prop())
}, reject)
}
prop.catch = function (reject) {
return promise.then(function () {
return prop()
}, reject)
}
prop.finally = function (callback) {
return promise.then(function (value) {
return simpleResolve(callback(), function () {
return value
})
}, function (reason) {
return simpleResolve(callback(), function () {
throw reason
})
})
}
return prop
}
m.prop = function (store) { m.prop = function (store) {
if ((store != null && isObject(store) || isFunction(store)) && if (isPromise(store)) {
isFunction(store.then)) {
return propify(store) return propify(store)
} else { } else {
return gettersetter(store) return gettersetter(store)
@ -1741,164 +1784,167 @@ void (function (global, factory) { // eslint-disable-line
function Deferred(onSuccess, onFailure) { function Deferred(onSuccess, onFailure) {
var self = this var self = this
var state = 0 var promiseValue
var promiseValue = 0
var next = [] var next = []
var func = push
self.promise = {} function coerce(value, next, error) {
if (isPromise(value)) {
return value.then(function (value) {
coerce(value, next, error)
}, function (e) {
coerce(e, error, error)
})
} else {
return next(promiseValue = value)
}
}
function resolve(deferred) {
deferred.resolve(promiseValue)
}
function reject(deferred) {
deferred.reject(promiseValue)
}
function push(deferred) {
next.push(deferred)
}
function init(promise) {
if (func !== reject) promise(promiseValue)
return promise
}
self.resolve = function (value) { self.resolve = function (value) {
if (!state) { if (func === push) {
promiseValue = value fire(RESOLVING, value, self)
state = RESOLVING
fire()
} }
return this return this
} }
self.reject = function (value) { self.reject = function (value) {
if (!state) { if (func === push) {
promiseValue = value fire(REJECTING, value, self)
state = REJECTING
fire()
} }
return this return this
} }
self.promise.then = function (onSuccess, onFailure) { self.promise = function (value) {
var deferred = new Deferred(onSuccess, onFailure) if (arguments.length) coerce(value, noop, noop)
if (state === RESOLVED) { return func !== reject ? promiseValue : undefined
deferred.resolve(promiseValue)
} else if (state === REJECTED) {
deferred.reject(promiseValue)
} else {
next.push(deferred)
}
return deferred.promise
} }
function finish(type) { self.promise.then = function (onSuccess, onFailure) {
state = type || REJECTED var deferred = new Deferred(onSuccess, onFailure)
forEach(next, function (deferred) { func(deferred)
if (state === RESOLVED) { return init(deferred.promise)
deferred.resolve(promiseValue) }
} else {
deferred.reject(promiseValue) self.promise.catch = function (callback) {
} return self.promise.then(null, callback)
}
self.promise.finally = function (callback) {
function _callback() {
var p = new Deferred().resolve(callback()).promise
if (func !== reject) p(promiseValue)
return p
}
return self.promise.then(function () {
return _callback().then(function () {
return promiseValue
})
}, function () {
_callback().then(function () {
throw promiseValue
})
}) })
} }
function thennable(then, success, fail, notThennable) { function run(callback) {
if (((promiseValue != null && isObject(promiseValue)) || func = callback
isFunction(promiseValue)) && isFunction(then)) { forEach(next, callback)
try { // Clear these (which hold all the extra references)
// count protects against abuse calls from spec checker finish = fire = null // eslint-disable-line no-func-assign
var count = 0 }
then.call(promiseValue, function (value) {
if (count++) return function finish(value, state) {
promiseValue = value coerce(value, function () {
success() run(state === RESOLVED ? resolve : reject)
}, function (value) { }, function () {
if (count++) return run(reject)
promiseValue = value })
fail() }
})
} catch (e) { function doThen(value, deferred) {
m.deferred.onerror(e) // count protects against abuse calls from spec checker
promiseValue = e var count = 0
fail()
} try {
} else { return value.then(function (value) {
notThennable() if (count++) return
fire(RESOLVING, value, deferred)
}, function (value) {
if (count++) return
fire(REJECTING, value, deferred)
})
} catch (e) {
m.deferred.onerror(e)
return fire(REJECTING, e, deferred)
} }
} }
function fire() { function notThennable(value, state, deferred) {
// check if it's a thenable
var then
try { try {
then = promiseValue && promiseValue.then if (state === RESOLVING && isFunction(onSuccess)) {
value = onSuccess(value)
} else if (state === REJECTING && isFunction(onFailure)) {
value = onFailure(value)
state = RESOLVING
}
} catch (e) { } catch (e) {
m.deferred.onerror(e) m.deferred.onerror(e)
promiseValue = e return finish(e, REJECTED)
state = REJECTING }
return fire()
if (value === deferred) {
return finish(TypeError(), REJECTED)
} else {
return finish(value, state === RESOLVING ? RESOLVED : REJECTED)
}
}
function fire(state, value, deferred) {
// check if it's a thenable
var thenable
try {
thenable = isPromise(value)
} catch (e) {
m.deferred.onerror(e)
return fire(REJECTING, e, deferred)
} }
if (state === REJECTING) { if (state === REJECTING) {
m.deferred.onerror(promiseValue) m.deferred.onerror(value)
} }
thennable(then, function () { if (thenable) {
state = RESOLVING return doThen(value, deferred)
fire() } else {
}, function () { return notThennable(value, state, deferred)
state = REJECTING }
fire()
}, function () {
try {
if (state === RESOLVING && isFunction(onSuccess)) {
promiseValue = onSuccess(promiseValue)
} else if (state === REJECTING && isFunction(onFailure)) {
promiseValue = onFailure(promiseValue)
state = RESOLVING
}
} catch (e) {
m.deferred.onerror(e)
promiseValue = e
return finish()
}
if (promiseValue === self) {
promiseValue = TypeError()
finish()
} else {
thennable(then, function () {
finish(RESOLVED)
}, finish, function () {
finish(state === RESOLVING && RESOLVED)
})
}
})
} }
} }
m.deferred = function () { m.deferred = function () {
var deferred = new Deferred() return new Deferred()
deferred.promise = propify(deferred.promise)
return deferred
} }
function propify(promise, initialValue) { m.deferred.prototype = Deferred.prototype
var prop = m.prop(initialValue) m.deferred.prototype.constructor = m.deferred
promise.then(prop)
prop.then = function (resolve, reject) {
return propify(promise.then(resolve, reject), initialValue)
}
prop.catch = prop.then.bind(null, null)
prop.finally = function (callback) {
function _callback() {
return m.deferred().resolve(callback()).promise
}
return prop.then(function (value) {
return propify(_callback().then(function () {
return value
}), initialValue)
}, function (reason) {
return propify(_callback().then(function () {
throw new Error(reason)
}), initialValue)
})
}
return prop
}
function isNativeError(e) { function isNativeError(e) {
return e instanceof EvalError || return e instanceof EvalError ||
@ -1917,7 +1963,7 @@ void (function (global, factory) { // eslint-disable-line
} }
m.sync = function (args) { m.sync = function (args) {
var deferred = m.deferred() var deferred = new Deferred()
var outstanding = args.length var outstanding = args.length
var results = new Array(outstanding) var results = new Array(outstanding)
var method = "resolve" var method = "resolve"
@ -2086,7 +2132,6 @@ void (function (global, factory) { // eslint-disable-line
m.request = function (options) { m.request = function (options) {
if (options.background !== true) m.startComputation() if (options.background !== true) m.startComputation()
var deferred = new Deferred() var deferred = new Deferred()
var serialize = identity var serialize = identity
@ -2146,7 +2191,7 @@ void (function (global, factory) { // eslint-disable-line
} }
ajax(options) ajax(options)
deferred.promise = propify(deferred.promise, options.initialValue) deferred.promise(options.initialValue)
return deferred.promise return deferred.promise
} }

2
mithril.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -117,7 +117,6 @@ describe("m.deferred()", function () {
expect(value2()).to.be.an("error") expect(value2()).to.be.an("error")
}) })
// FIXME: this is a bug.
it("synchronously throws subclasses of Errors on creation", function () { it("synchronously throws subclasses of Errors on creation", function () {
expect(function () { expect(function () {
m.deferred().reject(new TypeError()) m.deferred().reject(new TypeError())