Merge pull request #853 from isiahmeadows/prop-fix
Isolate m.prop() and m.deferred() implementations (mostly)
This commit is contained in:
commit
e63a6f37bd
5 changed files with 176 additions and 131 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
archive
|
||||||
|
|
|
||||||
301
mithril.js
301
mithril.js
|
|
@ -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
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
|
|
@ -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())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue