Isolate m.prop() and m.deferred() implementations (mostly)
This mostly isolates the implementations for both of these. Now, everything here calls the method itself, not any of the external methods. Few driveby fixes as well: 1. Git now ignores archive/ again (it's a build artifact, and can be removed when updating `master`) 2. Since I had to rewrite most of the Deferred implementation, the new version passes one of the skipped tests, so it is now enabled.
This commit is contained in:
parent
edc5dca238
commit
d7ef127be2
6 changed files with 170 additions and 137 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
archive
|
||||||
|
|
|
||||||
289
mithril.js
289
mithril.js
|
|
@ -1193,9 +1193,39 @@ 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 propify(promise) {
|
||||||
|
var prop = m.prop()
|
||||||
|
promise.then(prop)
|
||||||
|
|
||||||
|
prop.then = promise.then.bind(promise)
|
||||||
|
prop.catch = promise.then.bind(promise, undefined)
|
||||||
|
|
||||||
|
prop.finally = function (callback) {
|
||||||
|
function _callback() {
|
||||||
|
return m.deferred().resolve(callback()).promise
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(function (value) {
|
||||||
|
return _callback().then(function () {
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
}, function (reason) {
|
||||||
|
return _callback().then(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)
|
||||||
|
|
@ -1732,164 +1762,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 ||
|
||||||
|
|
@ -1908,7 +1941,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"
|
||||||
|
|
@ -2077,7 +2110,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
|
||||||
|
|
@ -2127,7 +2159,6 @@ void (function (global, factory) { // eslint-disable-line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
m.deferred.onerror(e)
|
|
||||||
response = e
|
response = e
|
||||||
doSuccess = false
|
doSuccess = false
|
||||||
}
|
}
|
||||||
|
|
@ -2138,7 +2169,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
6
mithril.min.js
vendored
6
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,8 +117,7 @@ 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 () {
|
||||||
xit("synchronously throws subclasses of Errors on creation", function () {
|
|
||||||
expect(function () {
|
expect(function () {
|
||||||
m.deferred().reject(new TypeError())
|
m.deferred().reject(new TypeError())
|
||||||
}).to.throw()
|
}).to.throw()
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,14 @@ describe("m.prop()", function () {
|
||||||
it("returns a thenable when wrapping a Mithril promise", function () {
|
it("returns a thenable when wrapping a Mithril promise", function () {
|
||||||
var defer = m.deferred()
|
var defer = m.deferred()
|
||||||
|
|
||||||
var prop = m.prop(defer.promise).then(function () {
|
var prop = m.prop(defer.promise)
|
||||||
|
|
||||||
|
var promise = prop.then(function () {
|
||||||
return "test2"
|
return "test2"
|
||||||
})
|
})
|
||||||
|
|
||||||
defer.resolve("test")
|
defer.resolve("test")
|
||||||
|
|
||||||
expect(prop()).to.equal("test2")
|
expect(promise()).to.equal("test2")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue