diff --git a/mithril.js b/mithril.js index f2d10db2..b8b96b9b 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ Mithril = m = new function app(window) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - + function m() { var args = arguments var hasAttrs = type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) @@ -19,9 +19,9 @@ Mithril = m = new function app(window) { } } if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") - + cell.children = hasAttrs ? args[2] : args[1] - + for (var attrName in attrs) { if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] else cell.attrs[attrName] = attrs[attrName] @@ -49,7 +49,7 @@ Mithril = m = new function app(window) { if (dataType == "[object Array]") { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 - + var DELETION = 1, INSERTION = 2 , MOVE = 3 var existing = {}, unkeyed = [], shouldMaintainIdentities = false for (var i = 0; i < cached.length; i++) { @@ -72,7 +72,7 @@ Mithril = m = new function app(window) { var actions = Object.keys(existing).map(function(key) {return existing[key]}) var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index}) var newCached = cached.slice() - + for (var i = 0, change; change = changes[i]; i++) { if (change.action == DELETION) { clear(cached[change.index].nodes, cached[change.index]) @@ -84,7 +84,7 @@ Mithril = m = new function app(window) { parentElement.insertBefore(dummy, parentElement.childNodes[change.index]) newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) } - + if (change.action == MOVE) { if (parentElement.childNodes[change.index] !== change.element) { parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) @@ -101,7 +101,7 @@ Mithril = m = new function app(window) { cached.nodes = [] for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) } - + for (var i = 0, cacheCount = 0; i < data.length; i++) { var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) if (item === undefined) continue @@ -123,7 +123,7 @@ Mithril = m = new function app(window) { if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } - + } else if (dataType == "[object Object]") { if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { @@ -417,7 +417,7 @@ Mithril = m = new function app(window) { if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true - + if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) @@ -509,49 +509,157 @@ Mithril = m = new function app(window) { } var none = {} - m.deferred = function() { - var resolvers = [], rejecters = [], resolved = none, rejected = none, promise = m.prop() - var object = { - resolve: function(value) { - if (resolved === none) promise(resolved = value) - for (var i = 0; i < resolvers.length; i++) resolvers[i](value) - resolvers.length = rejecters.length = 0 - }, - reject: function(value) { - if (rejected === none) rejected = value - for (var i = 0; i < rejecters.length; i++) rejecters[i](value) - resolvers.length = rejecters.length = 0 - }, - promise: promise - } - object.promise.resolvers = resolvers - object.promise.then = function(success, error) { - var next = m.deferred() - if (!success) success = identity - if (!error) error = identity - function callback(method, callback) { - return function(value) { - try { - var result = callback(value) - if (result && typeof result.then == "function") result.then(next[method], error) - else next[method](result !== undefined ? result : value) - } - catch (e) { - if (e instanceof Error && e.constructor !== Error) throw e - else next.reject(e) - } + m.deferred = function () { + + // Promiz.mithril.js | Zolmeister | MIT + function Deferred(fn, er) { + // states + // 0: pending + // 1: resolving + // 2: rejecting + // 3: resolved + // 4: rejected + var self = this, + state = 0, + val = 0, + next = []; + + self['promise'] = self + + self['resolve'] = function (v) { + if (!state) { + val = v + state = 1 + + fire() } + return this } - if (resolved !== none) callback("resolve", success)(resolved) - else if (rejected !== none) callback("reject", error)(rejected) - else { - resolvers.push(callback("resolve", success)) - rejecters.push(callback("reject", error)) + + self['reject'] = function (v) { + if (!state) { + val = v + state = 2 + + fire() + } + return this + } + + self['then'] = function (fn, er) { + var d = new Deferred(fn, er) + if (state == 3) { + d.resolve(val) + } + else if (state == 4) { + d.reject(val) + } + else { + next.push(d) + } + return d + } + + var finish = function (type) { + state = type || 4 + next.map(function (p) { + state == 3 && p.resolve(val) || p.reject(val) + }) + } + + // ref : reference to 'then' function + // cb, ec, cn : successCallback, failureCallback, notThennableCallback + function thennable (ref, cb, ec, cn) { + if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') { + try { + + // cnt protects against abuse calls from spec checker + var cnt = 0 + ref.call(val, function (v) { + if (cnt++) return + val = v + cb() + }, function (v) { + if (cnt++) return + val = v + ec() + }) + } catch (e) { + val = e + ec() + } + } else { + cn() + } + }; + + function fire() { + + // check if it's a thenable + var ref; + try { + ref = val && val.then + } catch (e) { + val = e + state = 2 + return fire() + } + thennable(ref, function () { + state = 1 + fire() + }, function () { + state = 2 + fire() + }, function () { + try { + if (state == 1 && typeof fn == 'function') { + val = fn(val) + } + + else if (state == 2 && typeof er == 'function') { + val = er(val) + state = 1 + } + } catch (e) { + val = e + return finish() + } + + if (val == self) { + val = TypeError() + finish() + } else thennable(ref, function () { + finish(3) + }, finish, function () { + finish(state == 1 && 3) + }) + + }) } - return next.promise } - return object - } + + function newPromisedProp(prop, promise) { + prop.then = function () { + var newProp = m.prop(prop()) + return newPromisedProp(newProp, + promise.then.apply(promise, arguments).then(newProp)) + } + prop.promise = prop + prop.resolve = function (val) { + prop(val) + promise = promise.resolve.apply(promise, arguments) + return prop + } + prop.reject = function () { + promise = promise.reject.apply(promise, arguments) + return prop + } + + return prop + } + + return newPromisedProp(m.prop(), new Deferred()) + } m.sync = function(args) { var method = "resolve" function synchronizer(pos, resolved) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index b0d54648..c4b86a32 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1322,7 +1322,7 @@ function testMithril(mock) { var error = m.prop("no error") var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error) mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === undefined && error().message === "error occurred" + return prop().message === "error occurred" && error().message === "error occurred" }) test(function() { var error = m.prop("no error"), exception @@ -1365,7 +1365,7 @@ function testMithril(mock) { test(function() { var value var deferred = m.deferred() - deferred.promise.then(null, function(data) {return "foo"}).then(null, function(data) {value = data}) + deferred.promise.then(null, function(data) {return "foo"}).then(function(data) {value = data}) deferred.reject("test") return value === "foo" }) @@ -1534,7 +1534,7 @@ function testMithril(mock) { controller.value = "foo" m.endComputation() mock.requestAnimationFrame.$resolve() - + return root.childNodes[0].nodeValue === "foo" })