|
1 | 1 | /*! dustjs-linkedin - v2.7.2
|
2 | 2 | * http://dustjs.com/
|
3 |
| -* Copyright (c) 2015 Aleksander Williams; Released under the MIT License */ |
| 3 | +* Copyright (c) 2021 Aleksander Williams; Released under the MIT License */ |
4 | 4 | (function (root, factory) {
|
5 | 5 | if (typeof define === 'function' && define.amd && define.amd.dust === true) {
|
6 | 6 | define('dust.core', [], factory);
|
|
70 | 70 | type = type || INFO;
|
71 | 71 | if (loggingLevels[type] >= loggingLevels[dust.debugLevel]) {
|
72 | 72 | log('[DUST:' + type + ']', message);
|
| 73 | + if (type === ERROR && dust.debugLevel === DEBUG && message instanceof Error && message.stack) { |
| 74 | + log('[DUST:' + type + ']', message.stack); |
| 75 | + } |
73 | 76 | }
|
74 | 77 | };
|
75 | 78 |
|
|
248 | 251 | };
|
249 | 252 |
|
250 | 253 | /**
|
251 |
| - * Decide somewhat-naively if something is a Thenable. |
| 254 | + * Decide somewhat-naively if something is a Thenable. Matches Promises A+ Spec, section 1.2 “thenable” is an object or function that defines a then method." |
252 | 255 | * @param elem {*} object to inspect
|
253 | 256 | * @return {Boolean} is `elem` a Thenable?
|
254 | 257 | */
|
255 | 258 | dust.isThenable = function(elem) {
|
256 |
| - return elem && |
257 |
| - typeof elem === 'object' && |
| 259 | + return elem && /* Beware: `typeof null` is `object` */ |
| 260 | + (typeof elem === 'object' || typeof elem === 'function') && |
258 | 261 | typeof elem.then === 'function';
|
259 | 262 | };
|
260 | 263 |
|
| 264 | + /** |
| 265 | + * Decide if an element is a function but not Thenable; it is prefereable to resolve a thenable function by its `.then` method. |
| 266 | + * @param elem {*} target of inspection |
| 267 | + * @return {Boolean} is `elem` a function without a `.then` property? |
| 268 | + */ |
| 269 | + dust.isNonThenableFunction = function(elem) { |
| 270 | + return typeof elem === 'function' && !dust.isThenable(elem); |
| 271 | + }; |
| 272 | + |
261 | 273 | /**
|
262 | 274 | * Decide very naively if something is a Stream.
|
263 | 275 | * @param elem {*} object to inspect
|
|
319 | 331 | this.options = options;
|
320 | 332 | this.blocks = blocks;
|
321 | 333 | this.templateName = templateName;
|
| 334 | + this._isContext = true; |
322 | 335 | }
|
323 | 336 |
|
324 | 337 | dust.makeBase = dust.context = function(global, options) {
|
325 | 338 | return new Context(undefined, global, options);
|
326 | 339 | };
|
327 | 340 |
|
| 341 | + dust.isContext = function(obj) { |
| 342 | + return typeof obj === "object" && obj._isContext === true; |
| 343 | + }; |
| 344 | + |
328 | 345 | /**
|
329 | 346 | * Factory function that creates a closure scope around a Thenable-callback.
|
330 | 347 | * Returns a function that can be passed to a Thenable that will resume a
|
|
338 | 355 | }
|
339 | 356 |
|
340 | 357 | Context.wrap = function(context, name) {
|
341 |
| - if (context instanceof Context) { |
| 358 | + if (dust.isContext(context)) { |
| 359 | + context.templateName = name; |
342 | 360 | return context;
|
343 | 361 | }
|
344 | 362 | return new Context(context, {}, {}, null, name);
|
|
429 | 447 | }
|
430 | 448 | }
|
431 | 449 |
|
432 |
| - if (typeof ctx === 'function') { |
| 450 | + if (dust.isNonThenableFunction(ctx)) { |
433 | 451 | fn = function() {
|
434 | 452 | try {
|
435 | 453 | return ctx.apply(ctxThis, arguments);
|
|
710 | 728 | return this;
|
711 | 729 | };
|
712 | 730 |
|
| 731 | + /** |
| 732 | + * Inserts a new chunk that can be used to asynchronously render or write to it |
| 733 | + * @param callback {Function} The function that will be called with the new chunk |
| 734 | + * @returns {Chunk} A copy of this chunk instance in order to further chain function calls on the chunk |
| 735 | + */ |
713 | 736 | Chunk.prototype.map = function(callback) {
|
714 | 737 | var cursor = new Chunk(this.root, this.next, this.taps),
|
715 | 738 | branch = new Chunk(this.root, cursor, this.taps);
|
|
725 | 748 | return cursor;
|
726 | 749 | };
|
727 | 750 |
|
| 751 | + /** |
| 752 | + * Like Chunk#map but additionally resolves a thenable. If the thenable succeeds the callback is invoked with |
| 753 | + * a new chunk that can be used to asynchronously render or write to it, otherwise if the thenable is rejected |
| 754 | + * then the error body is rendered if available, an error is logged, and the callback is never invoked. |
| 755 | + * @param {Chunk} The current chunk to insert a new chunk |
| 756 | + * @param thenable {Thenable} the target thenable to await |
| 757 | + * @param context {Context} context to use to render the deferred chunk |
| 758 | + * @param bodies {Object} may optionally contain an "error" for when the thenable is rejected |
| 759 | + * @param callback {Function} The function that will be called with the new chunk |
| 760 | + * @returns {Chunk} A copy of this chunk instance in order to further chain function calls on the chunk |
| 761 | + */ |
| 762 | + function mapThenable(chunk, thenable, context, bodies, callback) { |
| 763 | + return chunk.map(function(asyncChunk) { |
| 764 | + thenable.then(function(data) { |
| 765 | + try { |
| 766 | + callback(asyncChunk, data); |
| 767 | + } catch (err) { |
| 768 | + // handle errors the same way Chunk#map would. This logic is only here since the thenable defers |
| 769 | + // logic such that the try / catch in Chunk#map would not capture it. |
| 770 | + dust.log(err, ERROR); |
| 771 | + asyncChunk.setError(err); |
| 772 | + } |
| 773 | + }, function(err) { |
| 774 | + dust.log('Unhandled promise rejection in `' + context.getTemplateName() + '`', INFO); |
| 775 | + asyncChunk.renderError(err, context, bodies).end(); |
| 776 | + }); |
| 777 | + }); |
| 778 | + } |
| 779 | + |
728 | 780 | Chunk.prototype.tap = function(tap) {
|
729 | 781 | var taps = this.taps;
|
730 | 782 |
|
|
746 | 798 | };
|
747 | 799 |
|
748 | 800 | Chunk.prototype.reference = function(elem, context, auto, filters) {
|
749 |
| - if (typeof elem === 'function') { |
| 801 | + if (dust.isNonThenableFunction(elem)) { |
750 | 802 | elem = elem.apply(context.current(), [this, context, null, {auto: auto, filters: filters}]);
|
751 | 803 | if (elem instanceof Chunk) {
|
752 | 804 | return elem;
|
|
771 | 823 | chunk = this,
|
772 | 824 | i, len, head;
|
773 | 825 |
|
774 |
| - if (typeof elem === 'function' && !dust.isTemplateFn(elem)) { |
| 826 | + if (dust.isNonThenableFunction(elem) && !dust.isTemplateFn(elem)) { |
775 | 827 | try {
|
776 | 828 | elem = elem.apply(context.current(), [this, context, bodies, params]);
|
777 | 829 | } catch(err) {
|
|
846 | 898 | var body = bodies.block,
|
847 | 899 | skip = bodies['else'];
|
848 | 900 |
|
| 901 | + if (dust.isThenable(elem)) { |
| 902 | + return mapThenable(this, elem, context, bodies, function(chunk, data) { |
| 903 | + chunk.exists(data, context, bodies).end(); |
| 904 | + }); |
| 905 | + } |
| 906 | + |
849 | 907 | if (!dust.isEmpty(elem)) {
|
850 | 908 | if (body) {
|
851 | 909 | return body(this, context);
|
|
861 | 919 | var body = bodies.block,
|
862 | 920 | skip = bodies['else'];
|
863 | 921 |
|
| 922 | + if (dust.isThenable(elem)) { |
| 923 | + return mapThenable(this, elem, context, bodies, function(chunk, data) { |
| 924 | + chunk.notexists(data, context, bodies).end(); |
| 925 | + }); |
| 926 | + } |
| 927 | + |
864 | 928 | if (dust.isEmpty(elem)) {
|
865 | 929 | if (body) {
|
866 | 930 | return body(this, context);
|
|
901 | 965 | // The eventual result of evaluating `elem` is a partial name
|
902 | 966 | // Load the partial after getting its name and end the async chunk
|
903 | 967 | return this.capture(elem, context, function(name, chunk) {
|
904 |
| - partialContext.templateName = name; |
905 | 968 | load(name, chunk, partialContext).end();
|
906 | 969 | });
|
907 | 970 | } else {
|
908 |
| - partialContext.templateName = elem; |
909 | 971 | return load(elem, this, partialContext);
|
910 | 972 | }
|
911 | 973 | };
|
|
957 | 1019 | * @return {Chunk}
|
958 | 1020 | */
|
959 | 1021 | Chunk.prototype.await = function(thenable, context, bodies, auto, filters) {
|
960 |
| - return this.map(function(chunk) { |
961 |
| - thenable.then(function(data) { |
962 |
| - if (bodies) { |
963 |
| - chunk = chunk.section(data, context, bodies); |
964 |
| - } else { |
965 |
| - // Actually a reference. Self-closing sections don't render |
966 |
| - chunk = chunk.reference(data, context, auto, filters); |
967 |
| - } |
968 |
| - chunk.end(); |
969 |
| - }, function(err) { |
970 |
| - var errorBody = bodies && bodies.error; |
971 |
| - if(errorBody) { |
972 |
| - chunk.render(errorBody, context.push(err)).end(); |
973 |
| - } else { |
974 |
| - dust.log('Unhandled promise rejection in `' + context.getTemplateName() + '`', INFO); |
975 |
| - chunk.end(); |
976 |
| - } |
977 |
| - }); |
| 1022 | + return mapThenable(this, thenable, context, bodies, function(chunk, data) { |
| 1023 | + if (bodies) { |
| 1024 | + chunk.section(data, context, bodies).end(); |
| 1025 | + } else { |
| 1026 | + // Actually a reference. Self-closing sections don't render |
| 1027 | + chunk.reference(data, context, auto, filters).end(); |
| 1028 | + } |
978 | 1029 | });
|
979 | 1030 | };
|
980 | 1031 |
|
| 1032 | + /** |
| 1033 | + * Render an error body if available |
| 1034 | + * @param err {Error} error that occurred |
| 1035 | + * @param context {Context} context to use to render the error |
| 1036 | + * @param bodies {Object} may optionally contain an "error" which will be rendered |
| 1037 | + * @return {Chunk} |
| 1038 | + */ |
| 1039 | + Chunk.prototype.renderError = function(err, context, bodies) { |
| 1040 | + var errorBody = bodies && bodies.error; |
| 1041 | + if (errorBody) { |
| 1042 | + return this.render(errorBody, context.push(err)); |
| 1043 | + } |
| 1044 | + return this; |
| 1045 | + }; |
| 1046 | + |
981 | 1047 | /**
|
982 | 1048 | * Reserve a chunk to be evaluated with the contents of a streamable.
|
983 | 1049 | * Currently an error event will bomb out the stream. Once an error
|
|
989 | 1055 | * @return {Chunk}
|
990 | 1056 | */
|
991 | 1057 | Chunk.prototype.stream = function(stream, context, bodies, auto, filters) {
|
992 |
| - var body = bodies && bodies.block, |
993 |
| - errorBody = bodies && bodies.error; |
| 1058 | + var body = bodies && bodies.block; |
994 | 1059 | return this.map(function(chunk) {
|
995 | 1060 | var ended = false;
|
996 | 1061 | stream
|
|
1012 | 1077 | if(ended) {
|
1013 | 1078 | return;
|
1014 | 1079 | }
|
1015 |
| - if(errorBody) { |
1016 |
| - chunk.render(errorBody, context.push(err)); |
1017 |
| - } else { |
1018 |
| - dust.log('Unhandled stream error in `' + context.getTemplateName() + '`', INFO); |
1019 |
| - } |
| 1080 | + chunk.renderError(err, context, bodies); |
| 1081 | + dust.log('Unhandled stream error in `' + context.getTemplateName() + '`', INFO); |
1020 | 1082 | if(!ended) {
|
1021 | 1083 | ended = true;
|
1022 | 1084 | chunk.end();
|
|
1143 | 1205 | if (typeof define === "function" && define.amd && define.amd.dust === true) {
|
1144 | 1206 | define(["require", "dust.core"], function(require, dust) {
|
1145 | 1207 | dust.onLoad = function(name, cb) {
|
1146 |
| - require([name], function() { |
1147 |
| - cb(); |
| 1208 | + require([name], function(tmpl) { |
| 1209 | + cb(null, tmpl); |
1148 | 1210 | });
|
1149 | 1211 | };
|
1150 | 1212 | return dust;
|
|
0 commit comments