diff --git a/README.md b/README.md index 50a85a33..67391d79 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.62 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (8.63 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. diff --git a/docs/change-log.md b/docs/change-log.md index 5df143f6..f49329ec 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -47,6 +47,13 @@ --- +### v1.1.7 + +- Promise polyfill implementation separated from polyfilling logic. +- `PromisePolyfill` is now available on the exported/global `m`. + +--- + ### v1.1.6 - core: render() function can no longer prevent from changing `document.activeElement` in lifecycle hooks ([#1988](https://github.com/MithrilJS/mithril.js/pull/1988), [@purplecode](https://github.com/purplecode)) diff --git a/docs/promise.md b/docs/promise.md index 8091c2e2..dc93f92d 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -26,6 +26,8 @@ A [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/G A Promise is a mechanism for working with asynchronous computations. +Mithril provides a polyfill when the environment does not support Promises. The polyfill can also be referenced specifically via `m.PromisePolyfill`. + --- ### Signature diff --git a/index.js b/index.js index 6e69cea4..123ba4ca 100644 --- a/index.js +++ b/index.js @@ -17,5 +17,6 @@ m.parseQueryString = require("./querystring/parse") m.buildQueryString = require("./querystring/build") m.version = "bleeding-edge" m.vnode = require("./render/vnode") +m.PromisePolyfill = require("./promise/polyfill") module.exports = m diff --git a/mithril.js b/mithril.js index 2bddf8de..1cf7bd5c 100644 --- a/mithril.js +++ b/mithril.js @@ -237,7 +237,7 @@ var buildQueryString = function(object) { } } var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") -var _8 = function($window, Promise) { +var _9 = function($window, Promise) { var callbackCount = 0 var oncompletion function setCompletionCallback(callback) {oncompletion = callback} @@ -386,7 +386,7 @@ var _8 = function($window, Promise) { } return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } -var requestService = _8(window, PromisePolyfill) +var requestService = _9(window, PromisePolyfill) var coreRenderer = function($window) { var $doc = $window.document var $emptyFragment = $doc.createDocumentFragment() @@ -1188,7 +1188,7 @@ function throttle(callback) { } } } -var _11 = function($window, throttleMock) { +var _12 = function($window, throttleMock) { var renderService = coreRenderer($window) renderService.setEventCallback(function(e) { if (e.redraw === false) e.redraw = undefined @@ -1214,9 +1214,9 @@ var _11 = function($window, throttleMock) { redraw.sync = sync return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } -var redrawService = _11(window) +var redrawService = _12(window) requestService.setCompletionCallback(redrawService.redraw) -var _16 = function(redrawService0) { +var _17 = function(redrawService0) { return function(root, component) { if (component === null) { redrawService0.render(root, []) @@ -1233,7 +1233,7 @@ var _16 = function(redrawService0) { run0() } } -m.mount = _16(redrawService) +m.mount = _17(redrawService) var Promise = PromisePolyfill var parseQueryString = function(string) { if (string === "" || string == null) return {} @@ -1361,7 +1361,7 @@ var coreRouter = function($window) { } return router } -var _20 = function($window, redrawService0) { +var _21 = function($window, redrawService0) { var routeService = coreRouter($window) var identity = function(v) {return v} var render1, component, attrs3, currentPath, lastUpdate @@ -1429,14 +1429,14 @@ var _20 = function($window, redrawService0) { } return route } -m.route = _20(window, redrawService) +m.route = _21(window, redrawService) m.withAttr = function(attrName, callback, context) { return function(e) { callback.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) } } -var _28 = coreRenderer(window) -m.render = _28.render +var _29 = coreRenderer(window) +m.render = _29.render m.redraw = redrawService.redraw m.request = requestService.request m.jsonp = requestService.jsonp @@ -1444,6 +1444,7 @@ m.parseQueryString = parseQueryString m.buildQueryString = buildQueryString m.version = "1.1.3" m.vnode = Vnode +m.PromisePolyfill = PromisePolyfill if (typeof module !== "undefined") module["exports"] = m else window.m = m }()); \ No newline at end of file diff --git a/mithril.min.js b/mithril.min.js index f08ea8cf..06d9a17a 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,46 +1,46 @@ -(function(){function t(a,d,e,h,p,l){return{tag:a,key:d,attrs:e,children:h,text:p,dom:l,domSize:void 0,state:void 0,events:void 0,instance:void 0,skip:!1}}function Q(a){for(var d in a)if(G.call(a,d))return!1;return!0}function x(a){var d=arguments[1],e=2;if(null==a||"string"!==typeof a&&"function"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a){var h;if(!(h=R[a])){var p="div";for(var l=[],k={};h=V.exec(a);){var q=h[1], -r=h[2];""===q&&""!==r?p=r:"#"===q?k.id=r:"."===q?l.push(r):"["===h[3][0]&&((q=h[6])&&(q=q.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===h[4]?l.push(q):k[h[4]]=""===q?q:q||!0)}0b.indexOf("?")?"?":"&";b+=e+d}return b}function k(b){try{return""!==b?JSON.parse(b):null}catch(C){throw Error(b);}}function q(b){return b.responseText}function r(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dm.status||304===m.status||Z.test(b.url))d(r(b.type,a));else{var h=Error(m.responseText);h.code=m.status;h.response=a;e(h)}}catch(H){e(H)}};h&&null!=b.data?m.send(b.data):m.send()});return!0===b.background?C:A(C)},jsonp:function(b,k){var A=e();b=h(b, -k);var q=new d(function(d,e){var h=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=a.document.createElement("script");a[h]=function(e){k.parentNode.removeChild(k);d(r(b.type,e));delete a[h]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete a[h]};null==b.data&&(b.data={});b.url=p(b.url,b.data);b.data[b.callbackKey||"callback"]=h;k.src=l(b.url,b.data);a.document.documentElement.appendChild(k)});return!0===b.background?q:A(q)},setCompletionCallback:function(b){u= -b}}}(window,B),U=function(a){function d(g,c){if(g.state!==c)throw Error("`vnode.state` must not be modified");}function e(g){var c=g.state;try{return this.apply(c,arguments)}finally{d(g,c)}}function h(g,c,f,b,a,d,e){for(;fb.indexOf("?")?"?":"&";b+=e+d}return b}function k(b){try{return""!==b?JSON.parse(b):null}catch(B){throw Error(b);}}function q(b){return b.responseText}function r(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dm.status||304===m.status||Z.test(b.url))d(r(b.type,a));else{var h=Error(m.responseText);h.code=m.status;h.response=a;e(h)}}catch(H){e(H)}};h&&null!=b.data?m.send(b.data):m.send()});return!0===b.background?B:A(B)},jsonp:function(b,k){var A=e();b=h(b, +k);var q=new d(function(d,e){var h=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=a.document.createElement("script");a[h]=function(e){k.parentNode.removeChild(k);d(r(b.type,e));delete a[h]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete a[h]};null==b.data&&(b.data={});b.url=p(b.url,b.data);b.data[b.callbackKey||"callback"]=h;k.src=l(b.url,b.data);a.document.documentElement.appendChild(k)});return!0===b.background?q:A(q)},setCompletionCallback:function(b){z= +b}}}(window,t),V=function(a){function d(g,c){if(g.state!==c)throw Error("`vnode.state` must not be modified");}function e(g){var c=g.state;try{return this.apply(c,arguments)}finally{d(g,c)}}function h(g,c,f,b,a,d,e){for(;f=v&&t>=n;)if(w=c[v],z=f[n],y=C&&v>=q,w===z&&!y&&!a||null==w&&null==z)v++,n++;else if(null==w)(E||null==z.key)&&p(g,f[n],d,k,u(c,++n,q,e)),v++;else if(null==z){if(E||null==w.key)A(c,n,n+1,f,a),v++;n++}else if(w.key===z.key)v++,n++,r(g,w,z,d,u(c,v,q,e),y||a,k),y&&w.tag===z.tag&&b(g,m(z),e);else if(w=c[l],y=C&&l>=q,w!==z||y||a)if(null==w)l--;else if(null== -z)n++;else if(w.key===z.key)r(g,w,z,d,u(c,l+1,q,e),y||a,k),(y&&w.tag===z.tag||n=v&&t>=n;){w=c[l];z=f[t];y=C&&l>=q;if(w!==z||y||a)if(null==w)l--;else{if(null!=z)if(w.key===z.key)r(g,w,z,d,u(c,l+1,q,e),y||a,k),y&&w.tag===z.tag&&b(g,m(z),e),null!=w.dom&&(e=w.dom),l--;else{if(!I){I=c;E=l;w={};for(y=0;y=q,r(g,w,z,d,u(c,l+1,q,e),y||a,k), -b(g,m(z),e),w.skip=!0,null!=w.dom&&(e=w.dom)):e=p(g,z,d,k,e))}t--}else l--,t--;if(t=g;l--)c[l].skip?c[l].skip=!1:B(c[l],f)}}}function r(g,c,f,a,b,d,h){var n=c.tag;if(n===f.tag){f.state=c.state;f.events=c.events;var A;if(A=!d){var u,E;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(u=e.call(f.attrs.onbeforeupdate,f,c));"string"!==typeof f.tag&&"function"===typeof f.state.onbeforeupdate&&(E=e.call(f.state.onbeforeupdate, -f,c));void 0===u&&void 0===E||u||E?A=!1:(f.dom=c.dom,f.domSize=c.domSize,f.instance=c.instance,A=!0)}if(!A)if("string"===typeof n)switch(null!=f.attrs&&(d?(f.state={},K(f.attrs,f,a)):M(f.attrs,f,a)),n){case "#":c.children.toString()!==f.children.toString()&&(c.dom.nodeValue=f.children);f.dom=c.dom;break;case "<":c.children!==f.children?(m(c),l(g,f,b)):(f.dom=c.dom,f.domSize=c.domSize);break;case "[":q(g,c.children,f.children,d,a,b,h);c=0;a=f.children;f.dom=null;if(null!=a){for(d=0;d=E&&t>=n;)if(v=c[E],y=f[n],u=B&&E>=q,v===y&&!u&&!a||null==v&&null==y)E++,n++;else if(null==v)(D||null==y.key)&&p(g,f[n],d,k,z(c,++n,q,e)),E++;else if(null==y){if(D||null==v.key)A(c,n,n+1,f,a),E++;n++}else if(v.key===y.key)E++,n++,r(g,v,y,d,z(c,E,q,e),u||a,k),u&&v.tag===y.tag&&b(g,m(y),e);else if(v=c[l],u=B&&l>=q,v!==y||u||a)if(null==v)l--;else if(null== +y)n++;else if(v.key===y.key)r(g,v,y,d,z(c,l+1,q,e),u||a,k),(u&&v.tag===y.tag||n=E&&t>=n;){v=c[l];y=f[t];u=B&&l>=q;if(v!==y||u||a)if(null==v)l--;else{if(null!=y)if(v.key===y.key)r(g,v,y,d,z(c,l+1,q,e),u||a,k),u&&v.tag===y.tag&&b(g,m(y),e),null!=v.dom&&(e=v.dom),l--;else{if(!x){x=c;D=l;v={};for(u=0;u=q,r(g,v,y,d,z(c,l+1,q,e),u||a,k), +b(g,m(y),e),v.skip=!0,null!=v.dom&&(e=v.dom)):e=p(g,y,d,k,e))}t--}else l--,t--;if(t=g;l--)c[l].skip?c[l].skip=!1:I(c[l],f)}}}function r(g,c,f,a,b,d,h){var n=c.tag;if(n===f.tag){f.state=c.state;f.events=c.events;var A;if(A=!d){var z,D;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(z=e.call(f.attrs.onbeforeupdate,f,c));"string"!==typeof f.tag&&"function"===typeof f.state.onbeforeupdate&&(D=e.call(f.state.onbeforeupdate, +f,c));void 0===z&&void 0===D||z||D?A=!1:(f.dom=c.dom,f.domSize=c.domSize,f.instance=c.instance,A=!0)}if(!A)if("string"===typeof n)switch(null!=f.attrs&&(d?(f.state={},K(f.attrs,f,a)):N(f.attrs,f,a)),n){case "#":c.children.toString()!==f.children.toString()&&(c.dom.nodeValue=f.children);f.dom=c.dom;break;case "<":c.children!==f.children?(m(c),l(g,f,b)):(f.dom=c.dom,f.domSize=c.domSize);break;case "[":q(g,c.children,f.children,d,a,b,h);c=0;a=f.children;f.dom=null;if(null!=a){for(d=0;d 0) return + fn(value) + } + } + var onerror = run(rejectCurrent) + try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} + } + + executeOnce(executor) +} +PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { + var self = this, instance = self._instance + function handle(callback, list, next, state) { + list.push(function(value) { + if (typeof callback !== "function") next(value) + else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} + }) + if (typeof instance.retry === "function" && state === instance.state) instance.retry() + } + var resolveNext, rejectNext + var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) + handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) + return promise +} +PromisePolyfill.prototype.catch = function(onRejection) { + return this.then(null, onRejection) +} +PromisePolyfill.prototype.finally = function(callback) { + return this.then( + function(value) { + return PromisePolyfill.resolve(callback()).then(function() { + return value + }) + }, + function(reason) { + return PromisePolyfill.resolve(callback()).then(function() { + return PromisePolyfill.reject(reason); + }) + } + ) +} +PromisePolyfill.resolve = function(value) { + if (value instanceof PromisePolyfill) return value + return new PromisePolyfill(function(resolve) {resolve(value)}) +} +PromisePolyfill.reject = function(value) { + return new PromisePolyfill(function(resolve, reject) {reject(value)}) +} +PromisePolyfill.all = function(list) { + return new PromisePolyfill(function(resolve, reject) { + var total = list.length, count = 0, values = [] + if (list.length === 0) resolve([]) + else for (var i = 0; i < list.length; i++) { + (function(i) { + function consume(value) { + count++ + values[i] = value + if (count === total) resolve(values) + } + if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { + list[i].then(consume, reject) + } + else consume(list[i]) + })(i) + } + }) +} +PromisePolyfill.race = function(list) { + return new PromisePolyfill(function(resolve, reject) { + for (var i = 0; i < list.length; i++) { + list[i].then(resolve, reject) + } + }) +} + +module.exports = PromisePolyfill diff --git a/promise/promise.js b/promise/promise.js index 51d124e8..7c6a2d11 100644 --- a/promise/promise.js +++ b/promise/promise.js @@ -1,113 +1,6 @@ "use strict" -/** @constructor */ -var PromisePolyfill = function(executor) { - if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") - if (typeof executor !== "function") throw new TypeError("executor must be a function") - var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) - var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} - var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout - function handler(list, shouldAbsorb) { - return function execute(value) { - var then - try { - if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { - if (value === self) throw new TypeError("Promise can't be resolved w/ itself") - executeOnce(then.bind(value)) - } - else { - callAsync(function() { - if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) - for (var i = 0; i < list.length; i++) list[i](value) - resolvers.length = 0, rejectors.length = 0 - instance.state = shouldAbsorb - instance.retry = function() {execute(value)} - }) - } - } - catch (e) { - rejectCurrent(e) - } - } - } - function executeOnce(then) { - var runs = 0 - function run(fn) { - return function(value) { - if (runs++ > 0) return - fn(value) - } - } - var onerror = run(rejectCurrent) - try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} - } - - executeOnce(executor) -} -PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { - var self = this, instance = self._instance - function handle(callback, list, next, state) { - list.push(function(value) { - if (typeof callback !== "function") next(value) - else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} - }) - if (typeof instance.retry === "function" && state === instance.state) instance.retry() - } - var resolveNext, rejectNext - var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) - handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) - return promise -} -PromisePolyfill.prototype.catch = function(onRejection) { - return this.then(null, onRejection) -} -PromisePolyfill.prototype.finally = function(callback) { - return this.then( - function(value) { - return PromisePolyfill.resolve(callback()).then(function() { - return value - }) - }, - function(reason) { - return PromisePolyfill.resolve(callback()).then(function() { - return PromisePolyfill.reject(reason); - }) - } - ) -} -PromisePolyfill.resolve = function(value) { - if (value instanceof PromisePolyfill) return value - return new PromisePolyfill(function(resolve) {resolve(value)}) -} -PromisePolyfill.reject = function(value) { - return new PromisePolyfill(function(resolve, reject) {reject(value)}) -} -PromisePolyfill.all = function(list) { - return new PromisePolyfill(function(resolve, reject) { - var total = list.length, count = 0, values = [] - if (list.length === 0) resolve([]) - else for (var i = 0; i < list.length; i++) { - (function(i) { - function consume(value) { - count++ - values[i] = value - if (count === total) resolve(values) - } - if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { - list[i].then(consume, reject) - } - else consume(list[i]) - })(i) - } - }) -} -PromisePolyfill.race = function(list) { - return new PromisePolyfill(function(resolve, reject) { - for (var i = 0; i < list.length; i++) { - list[i].then(resolve, reject) - } - }) -} +var PromisePolyfill = require("./polyfill") if (typeof window !== "undefined") { if (typeof window.Promise === "undefined") { diff --git a/promise/tests/test-promise.js b/promise/tests/test-promise.js index 7bddbb5d..4e85c9a4 100644 --- a/promise/tests/test-promise.js +++ b/promise/tests/test-promise.js @@ -2,7 +2,7 @@ var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") -var Promise = require("../../promise/promise") +var Promise = require("../../promise/polyfill") o.spec("promise", function() { o.spec("constructor", function() {