├── .eslintrc ├── .gitignore ├── Makefile ├── README.md ├── lib ├── examples │ ├── demo.js │ └── demo.js.map ├── tracedpromise.js └── tracedpromise.js.map ├── package.json └── src ├── examples └── demo.js └── tracedpromise.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "env": { 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "rules": { 9 | "array-bracket-spacing": [0], 10 | "consistent-return": [0], 11 | "func-names": [0], 12 | "guard-for-in": [0], 13 | "indent": [2, 4], 14 | "key-spacing": [2, { "align": "colon", "beforeColon": true, "afterColon": true }], 15 | "max-len": [2, 120, 4], 16 | "new-cap": [0], 17 | "no-param-reassign": [0], 18 | "no-multi-spaces": [2, { "exceptions": { 19 | "AssignmentExpression": true, 20 | "VariableDeclarator": true, 21 | }} ], 22 | "no-restricted-syntax": [2, 23 | 'DebuggerStatement', 24 | 'LabeledStatement', 25 | 'WithStatement', 26 | ], 27 | "no-underscore-dangle": [0], 28 | "no-unused-vars": [2, { "vars": "all", "args" : "none" }], 29 | "no-use-before-define": [2, { "functions": false }], 30 | "no-console": [1], 31 | "object-shorthand" : [0], 32 | "prefer-const": [0], 33 | "prefer-rest-params" : [0], 34 | 35 | "react/jsx-indent": [2, 4], 36 | // Disabled as the "exceptions" clause does not seem to currently work 37 | "spaced-comment": [0] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # http://stackoverflow.com/questions/3774568/makefile-issue-smart-way-to-scan-directory-tree-for-c-files 2 | recursive_wildcard = $(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call recursive_wildcard,$d/,$2)) 3 | 4 | CMD_BABEL = node node_modules/babel-cli/bin/babel.js 5 | CMD_ESLINT = node node_modules/eslint/bin/eslint.js 6 | SOURCES_JS = $(call recursive_wildcard,src/,*.js) 7 | COMPILED_JS = $(SOURCES_JS:src/%.js=lib/%.js) 8 | 9 | .PHONY: demo 10 | demo: build 11 | node lib/examples/demo.js 12 | 13 | # 14 | # build the ES6 JavaScript files and generate source maps 15 | # 16 | .PHONY: build 17 | build: node_modules $(COMPILED_JS) 18 | lib/%.js: src/%.js 19 | mkdir -p $(@D) 20 | $(CMD_BABEL) --presets es2015 $< -o $@ --source-maps 21 | 22 | node_modules: 23 | npm install 24 | 25 | .PHONY: clean 26 | clean: 27 | rm -rf node_modules/ 28 | rm -rf lib/ 29 | mkdir lib 30 | 31 | .PHONY: lint 32 | lint: 33 | $(CMD_ESLINT) --color --fix -c .eslintrc src 34 | 35 | .PHONY: publish 36 | publish: build lint 37 | @if [ $(shell git symbolic-ref --short -q HEAD) = "master" ]; then exit 0; else \ 38 | echo "Current git branch does not appear to be 'master'. Refusing to publish."; exit 1; \ 39 | fi 40 | npm version patch 41 | make build # rebuild with the new version number 42 | git push 43 | git push --tags 44 | npm whoami 45 | npm publish --access=public 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tracedpromise 2 | 3 | `TracedPromise` is a class based on the standard ES6 Promise API that adds [OpenTracing instrumentation](http://opentracing.io) to the promise. 4 | 5 | ## Getting started 6 | 7 | Install with `npm`. (*Note: you may need a relatively recent version of NPM to support scoped packages*.) 8 | 9 | ```bash 10 | npm install --save opentracing-tracedpromise 11 | ``` 12 | 13 | See `src/examples/demo.js` for a short example. 14 | -------------------------------------------------------------------------------- /lib/examples/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _opentracing = require('opentracing'); 4 | 5 | var opentracing = _interopRequireWildcard(_opentracing); 6 | 7 | var _lightstepTracer = require('lightstep-tracer'); 8 | 9 | var _lightstepTracer2 = _interopRequireDefault(_lightstepTracer); 10 | 11 | var _ = require('../..'); 12 | 13 | var _2 = _interopRequireDefault(_); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 18 | 19 | // Ensure the Node line numbers are accurate in stack traces 20 | require('source-map-support'); 21 | 22 | // Initialize the tracing implementation, in this case LightStep is used. 23 | // Replace '{your_access_token}' with your LightStep access token to send the 24 | // tracing data to your project. 25 | /* eslint-disable no-console */ 26 | opentracing.initGlobalTracer(new _lightstepTracer2.default.Tracer({ 27 | access_token: '{your_access_token}', 28 | component_name: 'TracedPromise' 29 | })); 30 | 31 | // Set up an initial span to track all the subsequent work 32 | var parent = opentracing.globalTracer().startSpan('Promises.all'); 33 | 34 | // Set up the child promises that run in parallel. 35 | // Simply timeouts are being used here. In a real world application, these might 36 | // be any asynchronous operation: file i/o, database transactions, network 37 | // requests, etc. 38 | var p1 = new _2.default(parent, 'p1', function (resolve, reject) { 39 | setTimeout(resolve, 100, 'one'); 40 | }); 41 | var p2 = new _2.default(parent, 'p2', function (resolve, reject, span) { 42 | var childSpan = opentracing.globalTracer().startSpan('p2Child', { childOf: span }); 43 | setTimeout(function (arg) { 44 | childSpan.finish(); 45 | resolve(arg); 46 | }, 200, 'two'); 47 | }); 48 | var p3 = new _2.default(parent, 'p3', function (resolve, reject) { 49 | setTimeout(resolve, 300, 'three'); 50 | }); 51 | var p4 = new _2.default(parent, 'p4', function (resolve, reject) { 52 | setTimeout(resolve, 400, 'four'); 53 | }); 54 | var p5 = new _2.default(parent, 'p5', function (resolve, reject) { 55 | setTimeout(reject, 250, 'failure!'); 56 | }); 57 | var p6Options = { 58 | references: [opentracing.followsFrom(parent.context())] 59 | }; 60 | var p6 = new _2.default(p6Options, 'p6', function (resolve, reject) { 61 | setTimeout(resolve, 600, 'six'); 62 | }); 63 | 64 | // Wait for the child promises to resolve or reject and then handle the result. 65 | _2.default.all(parent, [p1, p2, p3, p4, p5, p6]).then(function (value) { 66 | console.log('Resolved: ' + value); 67 | }, function (reason) { 68 | console.log('Rejected: ' + reason); 69 | }); 70 | 71 | //# sourceMappingURL=demo.js.map -------------------------------------------------------------------------------- /lib/examples/demo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/examples/demo.js"],"names":[],"mappings":";;AACA;;IAAY,W;;AACZ;;;;AACA;;;;;;;;AAEA;AACA,QAAQ,oBAAR;;AAEA;AACA;AACA;AAVA;AAWA,YAAY,gBAAZ,CAA6B,IAAI,0BAAU,MAAd,CAAqB;AAC9C,kBAAiB,qBAD6B;AAE9C,oBAAiB;AAF6B,CAArB,CAA7B;;AAKA;AACA,IAAI,SAAS,YAAY,YAAZ,GAA2B,SAA3B,CAAqC,cAArC,CAAb;;AAEA;AACA;AACA;AACA;AACA,IAAI,KAAK,eAAkB,MAAlB,EAA0B,IAA1B,EAAgC,UAAC,OAAD,EAAU,MAAV,EAAqB;AAC1D,eAAW,OAAX,EAAoB,GAApB,EAAyB,KAAzB;AACH,CAFQ,CAAT;AAGA,IAAI,KAAK,eAAkB,MAAlB,EAA0B,IAA1B,EAAgC,UAAC,OAAD,EAAU,MAAV,EAAkB,IAAlB,EAA2B;AAChE,QAAI,YAAY,YAAY,YAAZ,GACY,SADZ,CACsB,SADtB,EACiC,EAAE,SAAU,IAAZ,EADjC,CAAhB;AAEA,eAAW,UAAC,GAAD,EAAS;AAClB,kBAAU,MAAV;AACA,gBAAQ,GAAR;AACD,KAHD,EAGG,GAHH,EAGQ,KAHR;AAIH,CAPQ,CAAT;AAQA,IAAI,KAAK,eAAkB,MAAlB,EAA0B,IAA1B,EAAgC,UAAC,OAAD,EAAU,MAAV,EAAqB;AAC1D,eAAW,OAAX,EAAoB,GAApB,EAAyB,OAAzB;AACH,CAFQ,CAAT;AAGA,IAAI,KAAK,eAAkB,MAAlB,EAA0B,IAA1B,EAAgC,UAAC,OAAD,EAAU,MAAV,EAAqB;AAC1D,eAAW,OAAX,EAAoB,GAApB,EAAyB,MAAzB;AACH,CAFQ,CAAT;AAGA,IAAI,KAAK,eAAkB,MAAlB,EAA0B,IAA1B,EAAgC,UAAC,OAAD,EAAU,MAAV,EAAqB;AAC1D,eAAW,MAAX,EAAmB,GAAnB,EAAwB,UAAxB;AACH,CAFQ,CAAT;AAGA,IAAI,YAAY;AACZ,gBAAa,CAAE,YAAY,WAAZ,CAAwB,OAAO,OAAP,EAAxB,CAAF;AADD,CAAhB;AAGA,IAAI,KAAK,eAAkB,SAAlB,EAA6B,IAA7B,EAAmC,UAAC,OAAD,EAAU,MAAV,EAAqB;AAC7D,eAAW,OAAX,EAAoB,GAApB,EAAyB,KAAzB;AACH,CAFQ,CAAT;;AAIA;AACA,WAAc,GAAd,CAAkB,MAAlB,EAA0B,CAAC,EAAD,EAAK,EAAL,EAAS,EAAT,EAAa,EAAb,EAAiB,EAAjB,EAAqB,EAArB,CAA1B,EAAoD,IAApD,CAAyD,iBAAS;AAC9D,YAAQ,GAAR,gBAAyB,KAAzB;AACH,CAFD,EAEG,kBAAU;AACT,YAAQ,GAAR,gBAAyB,MAAzB;AACH,CAJD","file":"demo.js","sourcesContent":["/* eslint-disable no-console */\nimport * as opentracing from 'opentracing';\nimport lightstep from 'lightstep-tracer';\nimport TracedPromise from '../..';\n\n// Ensure the Node line numbers are accurate in stack traces\nrequire('source-map-support');\n\n// Initialize the tracing implementation, in this case LightStep is used.\n// Replace '{your_access_token}' with your LightStep access token to send the\n// tracing data to your project.\nopentracing.initGlobalTracer(new lightstep.Tracer({\n access_token : '{your_access_token}',\n component_name : 'TracedPromise',\n}));\n\n// Set up an initial span to track all the subsequent work\nlet parent = opentracing.globalTracer().startSpan('Promises.all');\n\n// Set up the child promises that run in parallel.\n// Simply timeouts are being used here. In a real world application, these might\n// be any asynchronous operation: file i/o, database transactions, network\n// requests, etc.\nlet p1 = new TracedPromise(parent, 'p1', (resolve, reject) => {\n setTimeout(resolve, 100, 'one');\n});\nlet p2 = new TracedPromise(parent, 'p2', (resolve, reject, span) => {\n let childSpan = opentracing.globalTracer()\n .startSpan('p2Child', { childOf : span });\n setTimeout((arg) => {\n childSpan.finish();\n resolve(arg);\n }, 200, 'two');\n});\nlet p3 = new TracedPromise(parent, 'p3', (resolve, reject) => {\n setTimeout(resolve, 300, 'three');\n});\nlet p4 = new TracedPromise(parent, 'p4', (resolve, reject) => {\n setTimeout(resolve, 400, 'four');\n});\nlet p5 = new TracedPromise(parent, 'p5', (resolve, reject) => {\n setTimeout(reject, 250, 'failure!');\n});\nlet p6Options = {\n references : [ opentracing.followsFrom(parent.context()) ],\n};\nlet p6 = new TracedPromise(p6Options, 'p6', (resolve, reject) => {\n setTimeout(resolve, 600, 'six');\n});\n\n// Wait for the child promises to resolve or reject and then handle the result.\nTracedPromise.all(parent, [p1, p2, p3, p4, p5, p6]).then(value => {\n console.log(`Resolved: ${value}`);\n}, reason => {\n console.log(`Rejected: ${reason}`);\n});\n"]} -------------------------------------------------------------------------------- /lib/tracedpromise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _opentracing = require('opentracing'); 10 | 11 | var opentracing = _interopRequireWildcard(_opentracing); 12 | 13 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | /* 18 | * wrapResolve is a helper that takes a standard ES6 Promise resolve callback 19 | * and returns a * wrapped function that first finishes the given `span` before 20 | * calling `f`. It is safe to call this function with a null or undefined span. 21 | */ 22 | function wrapResolve(span, f) { 23 | if (!span) { 24 | return f; 25 | } 26 | return function () { 27 | span.finish(); 28 | return f.apply(undefined, arguments); 29 | }; 30 | } 31 | 32 | /* 33 | * wrapReject is a helper that takes a standard ES6 Promise resolve callback 34 | * and returns a * wrapped function that first finishes the given `span` before 35 | * calling `f`. It is safe to call this function with a null or undefined span. 36 | */ 37 | function wrapReject(span, f) { 38 | if (!span) { 39 | return f; 40 | } 41 | return function () { 42 | span.setTag('error', true); 43 | span.finish(); 44 | return f.apply(undefined, arguments); 45 | }; 46 | } 47 | 48 | /* 49 | * chainFinishSpan is a helper that finishes the given `span` when the `promise` 50 | * either rejects or resolved. It returns a valid Promise that so that chaining 51 | * can continue. 52 | */ 53 | function chainFinishSpan(promise, span) { 54 | return promise.then(function (value) { 55 | span.finish(); 56 | return value; 57 | }, function (reason) { 58 | span.setTag('error', true); 59 | span.finish(); 60 | return Promise.reject(reason); 61 | }); 62 | } 63 | 64 | /** 65 | * TracedPromise adds OpenTracing instrumentation to a standard ES6 Promise. 66 | */ 67 | 68 | var TracedPromise = function () { 69 | /** 70 | * Constructs anew TracedPromise 71 | * 72 | * @param {Object} options - the options to used to create the span for this 73 | * promise or the parent span. Pass `null` for a promise that does 74 | * not have a parent. 75 | * @param {string} name - name to use for the span created internally by 76 | * the TracedPromise. 77 | * @param {Function} callback - callback to use to resolve the promise. The 78 | * signature and behavior should be that of a callback passed to a 79 | * standard ES6 Promise. 80 | */ 81 | function TracedPromise(options, name, callback) { 82 | _classCallCheck(this, TracedPromise); 83 | 84 | var opts = options; 85 | if (options instanceof opentracing.Span) { 86 | opts = { childOf: options }; 87 | } 88 | var span = opentracing.globalTracer().startSpan(name, opts); 89 | var wrappedCallback = function wrappedCallback(resolve, reject) { 90 | return callback(wrapResolve(span, resolve), wrapReject(span, reject), span); 91 | }; 92 | this._promise = new Promise(wrappedCallback); 93 | this._span = span; 94 | } 95 | 96 | /** 97 | * Has the same behavior as `Promise.then` with the addition that the 98 | * TracedPromise's internal span will also be finished. 99 | */ 100 | 101 | 102 | _createClass(TracedPromise, [{ 103 | key: 'then', 104 | value: function then(onFulfilled, onRejected) { 105 | return this._promise.then(wrapResolve(this._span, onFulfilled), wrapReject(this._span, onRejected)); 106 | } 107 | 108 | /** 109 | * Has the same behavior as `Promise.catch` with the addition that the 110 | * TracedPromise's internal span will also be finished. 111 | */ 112 | 113 | }, { 114 | key: 'catch', 115 | value: function _catch(onRejected) { 116 | return this._promise.catch(wrapReject(this._span, onRejected)); 117 | } 118 | 119 | /** 120 | * Has the same behavior as `Promise.all` with the addition that passed in 121 | * `span` will be finish as soon as the returned Promise resolves or rejects. 122 | */ 123 | 124 | }], [{ 125 | key: 'all', 126 | value: function all(span, arr) { 127 | return chainFinishSpan(Promise.all(arr), span); 128 | } 129 | 130 | /** 131 | * Has the same behavior as `Promise.race` with the addition that passed in 132 | * `span` will be finish as soon as the returned Promise resolves or rejects. 133 | */ 134 | 135 | }, { 136 | key: 'race', 137 | value: function race(span, arr) { 138 | return chainFinishSpan(Promise.race(arr), span); 139 | } 140 | 141 | /** 142 | * Equivalent to `Promise.reject`. 143 | */ 144 | 145 | }, { 146 | key: 'reject', 147 | value: function reject() { 148 | return Promise.reject.apply(Promise, arguments); 149 | } 150 | 151 | /** 152 | * Equivalent to `Promise.resolve`. 153 | */ 154 | 155 | }, { 156 | key: 'resolve', 157 | value: function resolve() { 158 | return Promise.resolved.apply(Promise, arguments); 159 | } 160 | }]); 161 | 162 | return TracedPromise; 163 | }(); 164 | 165 | exports.default = TracedPromise; 166 | 167 | //# sourceMappingURL=tracedpromise.js.map -------------------------------------------------------------------------------- /lib/tracedpromise.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/tracedpromise.js"],"names":[],"mappings":";;;;;;;;AAAA;;IAAY,W;;;;;;AAEZ;;;;;AAKA,SAAS,WAAT,CAAqB,IAArB,EAA2B,CAA3B,EAA8B;AAC1B,QAAI,CAAC,IAAL,EAAW;AACP,eAAO,CAAP;AACH;AACD,WAAO,YAAmB;AACtB,aAAK,MAAL;AACA,eAAO,6BAAP;AACH,KAHD;AAIH;;AAED;;;;;AAKA,SAAS,UAAT,CAAoB,IAApB,EAA0B,CAA1B,EAA6B;AACzB,QAAI,CAAC,IAAL,EAAW;AACP,eAAO,CAAP;AACH;AACD,WAAO,YAAmB;AACtB,aAAK,MAAL,CAAY,OAAZ,EAAqB,IAArB;AACA,aAAK,MAAL;AACA,eAAO,6BAAP;AACH,KAJD;AAKH;;AAED;;;;;AAKA,SAAS,eAAT,CAAyB,OAAzB,EAAkC,IAAlC,EAAwC;AACpC,WAAO,QAAQ,IAAR,CAAa,UAAC,KAAD,EAAW;AAC3B,aAAK,MAAL;AACA,eAAO,KAAP;AACH,KAHM,EAGJ,UAAC,MAAD,EAAY;AACX,aAAK,MAAL,CAAY,OAAZ,EAAqB,IAArB;AACA,aAAK,MAAL;AACA,eAAO,QAAQ,MAAR,CAAe,MAAf,CAAP;AACH,KAPM,CAAP;AAQH;;AAED;;;;IAGqB,a;AACjB;;;;;;;;;;;;AAYA,2BAAY,OAAZ,EAAqB,IAArB,EAA2B,QAA3B,EAAqC;AAAA;;AACjC,YAAI,OAAO,OAAX;AACA,YAAI,mBAAmB,YAAY,IAAnC,EAAyC;AACrC,mBAAO,EAAE,SAAU,OAAZ,EAAP;AACH;AACD,YAAI,OAAO,YAAY,YAAZ,GACY,SADZ,CACsB,IADtB,EAC4B,IAD5B,CAAX;AAEA,YAAI,kBAAkB,SAAlB,eAAkB,CAAC,OAAD,EAAU,MAAV;AAAA,mBAAqB,SACvC,YAAY,IAAZ,EAAkB,OAAlB,CADuC,EAEvC,WAAW,IAAX,EAAiB,MAAjB,CAFuC,EAGvC,IAHuC,CAArB;AAAA,SAAtB;AAKA,aAAK,QAAL,GAAgB,IAAI,OAAJ,CAAY,eAAZ,CAAhB;AACA,aAAK,KAAL,GAAa,IAAb;AACH;;AAED;;;;;;;;6BAIK,W,EAAa,U,EAAY;AAC1B,mBAAO,KAAK,QAAL,CAAc,IAAd,CACH,YAAY,KAAK,KAAjB,EAAwB,WAAxB,CADG,EAEH,WAAW,KAAK,KAAhB,EAAuB,UAAvB,CAFG,CAAP;AAIH;;AAED;;;;;;;+BAIM,U,EAAY;AACd,mBAAO,KAAK,QAAL,CAAc,KAAd,CAAoB,WAAW,KAAK,KAAhB,EAAuB,UAAvB,CAApB,CAAP;AACH;;AAED;;;;;;;4BAIW,I,EAAM,G,EAAK;AAClB,mBAAO,gBAAgB,QAAQ,GAAR,CAAY,GAAZ,CAAhB,EAAkC,IAAlC,CAAP;AACH;;AAED;;;;;;;6BAIY,I,EAAM,G,EAAK;AACnB,mBAAO,gBAAgB,QAAQ,IAAR,CAAa,GAAb,CAAhB,EAAmC,IAAnC,CAAP;AACH;;AAED;;;;;;iCAGuB;AACnB,mBAAO,QAAQ,MAAR,0BAAP;AACH;;AAED;;;;;;kCAGwB;AACpB,mBAAO,QAAQ,QAAR,0BAAP;AACH;;;;;;kBA5EgB,a","file":"tracedpromise.js","sourcesContent":["import * as opentracing from 'opentracing';\n\n/*\n * wrapResolve is a helper that takes a standard ES6 Promise resolve callback\n * and returns a * wrapped function that first finishes the given `span` before\n * calling `f`. It is safe to call this function with a null or undefined span.\n */\nfunction wrapResolve(span, f) {\n if (!span) {\n return f;\n }\n return function (...args) {\n span.finish();\n return f(...args);\n };\n}\n\n/*\n * wrapReject is a helper that takes a standard ES6 Promise resolve callback\n * and returns a * wrapped function that first finishes the given `span` before\n * calling `f`. It is safe to call this function with a null or undefined span.\n */\nfunction wrapReject(span, f) {\n if (!span) {\n return f;\n }\n return function (...args) {\n span.setTag('error', true);\n span.finish();\n return f(...args);\n };\n}\n\n/*\n * chainFinishSpan is a helper that finishes the given `span` when the `promise`\n * either rejects or resolved. It returns a valid Promise that so that chaining\n * can continue.\n */\nfunction chainFinishSpan(promise, span) {\n return promise.then((value) => {\n span.finish();\n return value;\n }, (reason) => {\n span.setTag('error', true);\n span.finish();\n return Promise.reject(reason);\n });\n}\n\n/**\n * TracedPromise adds OpenTracing instrumentation to a standard ES6 Promise.\n */\nexport default class TracedPromise {\n /**\n * Constructs anew TracedPromise\n *\n * @param {Object} options - the options to used to create the span for this\n * promise or the parent span. Pass `null` for a promise that does\n * not have a parent.\n * @param {string} name - name to use for the span created internally by\n * the TracedPromise.\n * @param {Function} callback - callback to use to resolve the promise. The\n * signature and behavior should be that of a callback passed to a\n * standard ES6 Promise.\n */\n constructor(options, name, callback) {\n let opts = options;\n if (options instanceof opentracing.Span) {\n opts = { childOf : options };\n }\n let span = opentracing.globalTracer()\n .startSpan(name, opts);\n let wrappedCallback = (resolve, reject) => callback(\n wrapResolve(span, resolve),\n wrapReject(span, reject),\n span\n );\n this._promise = new Promise(wrappedCallback);\n this._span = span;\n }\n\n /**\n * Has the same behavior as `Promise.then` with the addition that the\n * TracedPromise's internal span will also be finished.\n */\n then(onFulfilled, onRejected) {\n return this._promise.then(\n wrapResolve(this._span, onFulfilled),\n wrapReject(this._span, onRejected)\n );\n }\n\n /**\n * Has the same behavior as `Promise.catch` with the addition that the\n * TracedPromise's internal span will also be finished.\n */\n catch(onRejected) {\n return this._promise.catch(wrapReject(this._span, onRejected));\n }\n\n /**\n * Has the same behavior as `Promise.all` with the addition that passed in\n * `span` will be finish as soon as the returned Promise resolves or rejects.\n */\n static all(span, arr) {\n return chainFinishSpan(Promise.all(arr), span);\n }\n\n /**\n * Has the same behavior as `Promise.race` with the addition that passed in\n * `span` will be finish as soon as the returned Promise resolves or rejects.\n */\n static race(span, arr) {\n return chainFinishSpan(Promise.race(arr), span);\n }\n\n /**\n * Equivalent to `Promise.reject`.\n */\n static reject(...args) {\n return Promise.reject(...args);\n }\n\n /**\n * Equivalent to `Promise.resolve`.\n */\n static resolve(...args) {\n return Promise.resolved(...args);\n }\n}\n"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentracing-tracedpromise", 3 | "description": "A Promise class modified to allow use of OpenTracing", 4 | "version": "1.0.2", 5 | "license": "MIT", 6 | "repository": "https://github.com/opentracing-contrib/tracedpromise", 7 | "main": "lib/tracedpromise.js", 8 | "scripts": { 9 | "run": "make demo", 10 | "demo": "make demo" 11 | }, 12 | "dependencies": { 13 | "opentracing": "^0.13.0" 14 | }, 15 | "devDependencies": { 16 | "babel-cli": "^6.10.1", 17 | "babel-core": "^6.10.4", 18 | "babel-preset-es2015": "^6.9.0", 19 | "eslint": "2.9.x", 20 | "eslint-config-airbnb": "^9.0.1", 21 | "eslint-plugin-import": "^1.11.0", 22 | "eslint-plugin-jsx-a11y": "^2.0.1", 23 | "eslint-plugin-react": "^5.2.2", 24 | "lightstep-tracer": "^0.20.3", 25 | "source-map-loader": "^0.1.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/demo.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as opentracing from 'opentracing'; 3 | import lightstep from 'lightstep-tracer'; 4 | import TracedPromise from '../..'; 5 | 6 | // Ensure the Node line numbers are accurate in stack traces 7 | require('source-map-support'); 8 | 9 | // Initialize the tracing implementation, in this case LightStep is used. 10 | // Replace '{your_access_token}' with your LightStep access token to send the 11 | // tracing data to your project. 12 | opentracing.initGlobalTracer(new lightstep.Tracer({ 13 | access_token : '{your_access_token}', 14 | component_name : 'TracedPromise', 15 | })); 16 | 17 | // Set up an initial span to track all the subsequent work 18 | let parent = opentracing.globalTracer().startSpan('Promises.all'); 19 | 20 | // Set up the child promises that run in parallel. 21 | // Simply timeouts are being used here. In a real world application, these might 22 | // be any asynchronous operation: file i/o, database transactions, network 23 | // requests, etc. 24 | let p1 = new TracedPromise(parent, 'p1', (resolve, reject) => { 25 | setTimeout(resolve, 100, 'one'); 26 | }); 27 | let p2 = new TracedPromise(parent, 'p2', (resolve, reject, span) => { 28 | let childSpan = opentracing.globalTracer() 29 | .startSpan('p2Child', { childOf : span }); 30 | setTimeout((arg) => { 31 | childSpan.finish(); 32 | resolve(arg); 33 | }, 200, 'two'); 34 | }); 35 | let p3 = new TracedPromise(parent, 'p3', (resolve, reject) => { 36 | setTimeout(resolve, 300, 'three'); 37 | }); 38 | let p4 = new TracedPromise(parent, 'p4', (resolve, reject) => { 39 | setTimeout(resolve, 400, 'four'); 40 | }); 41 | let p5 = new TracedPromise(parent, 'p5', (resolve, reject) => { 42 | setTimeout(reject, 250, 'failure!'); 43 | }); 44 | let p6Options = { 45 | references : [ opentracing.followsFrom(parent.context()) ], 46 | }; 47 | let p6 = new TracedPromise(p6Options, 'p6', (resolve, reject) => { 48 | setTimeout(resolve, 600, 'six'); 49 | }); 50 | 51 | // Wait for the child promises to resolve or reject and then handle the result. 52 | TracedPromise.all(parent, [p1, p2, p3, p4, p5, p6]).then(value => { 53 | console.log(`Resolved: ${value}`); 54 | }, reason => { 55 | console.log(`Rejected: ${reason}`); 56 | }); 57 | -------------------------------------------------------------------------------- /src/tracedpromise.js: -------------------------------------------------------------------------------- 1 | import * as opentracing from 'opentracing'; 2 | 3 | /* 4 | * wrapResolve is a helper that takes a standard ES6 Promise resolve callback 5 | * and returns a * wrapped function that first finishes the given `span` before 6 | * calling `f`. It is safe to call this function with a null or undefined span. 7 | */ 8 | function wrapResolve(span, f) { 9 | if (!span) { 10 | return f; 11 | } 12 | return function (...args) { 13 | span.finish(); 14 | return f(...args); 15 | }; 16 | } 17 | 18 | /* 19 | * wrapReject is a helper that takes a standard ES6 Promise resolve callback 20 | * and returns a * wrapped function that first finishes the given `span` before 21 | * calling `f`. It is safe to call this function with a null or undefined span. 22 | */ 23 | function wrapReject(span, f) { 24 | if (!span) { 25 | return f; 26 | } 27 | return function (...args) { 28 | span.setTag('error', true); 29 | span.finish(); 30 | return f(...args); 31 | }; 32 | } 33 | 34 | /* 35 | * chainFinishSpan is a helper that finishes the given `span` when the `promise` 36 | * either rejects or resolved. It returns a valid Promise that so that chaining 37 | * can continue. 38 | */ 39 | function chainFinishSpan(promise, span) { 40 | return promise.then((value) => { 41 | span.finish(); 42 | return value; 43 | }, (reason) => { 44 | span.setTag('error', true); 45 | span.finish(); 46 | return Promise.reject(reason); 47 | }); 48 | } 49 | 50 | /** 51 | * TracedPromise adds OpenTracing instrumentation to a standard ES6 Promise. 52 | */ 53 | export default class TracedPromise { 54 | /** 55 | * Constructs anew TracedPromise 56 | * 57 | * @param {Object} options - An `opentracing.SpanOptions` used to create the 58 | * span for this promise or its parent span. Pass `null` for a 59 | * promise that does not have a parent. See 60 | * https://opentracing-javascript.surge.sh/interfaces/spanoptions.html 61 | * @param {string} name - name to use for the span created internally by 62 | * the TracedPromise. 63 | * @param {Function} callback - callback to use to resolve the promise. The 64 | * signature and behavior should be that of a callback passed to a 65 | * standard ES6 Promise. 66 | */ 67 | constructor(options, name, callback) { 68 | let opts = options; 69 | if (options instanceof opentracing.Span) { 70 | opts = { childOf : options }; 71 | } 72 | let span = opentracing.globalTracer() 73 | .startSpan(name, opts); 74 | let wrappedCallback = (resolve, reject) => callback( 75 | wrapResolve(span, resolve), 76 | wrapReject(span, reject), 77 | span 78 | ); 79 | this._promise = new Promise(wrappedCallback); 80 | this._span = span; 81 | } 82 | 83 | /** 84 | * Has the same behavior as `Promise.then` with the addition that the 85 | * TracedPromise's internal span will also be finished. 86 | */ 87 | then(onFulfilled, onRejected) { 88 | return this._promise.then( 89 | wrapResolve(this._span, onFulfilled), 90 | wrapReject(this._span, onRejected) 91 | ); 92 | } 93 | 94 | /** 95 | * Has the same behavior as `Promise.catch` with the addition that the 96 | * TracedPromise's internal span will also be finished. 97 | */ 98 | catch(onRejected) { 99 | return this._promise.catch(wrapReject(this._span, onRejected)); 100 | } 101 | 102 | /** 103 | * Has the same behavior as `Promise.all` with the addition that passed in 104 | * `span` will be finish as soon as the returned Promise resolves or rejects. 105 | */ 106 | static all(span, arr) { 107 | return chainFinishSpan(Promise.all(arr), span); 108 | } 109 | 110 | /** 111 | * Has the same behavior as `Promise.race` with the addition that passed in 112 | * `span` will be finish as soon as the returned Promise resolves or rejects. 113 | */ 114 | static race(span, arr) { 115 | return chainFinishSpan(Promise.race(arr), span); 116 | } 117 | 118 | /** 119 | * Equivalent to `Promise.reject`. 120 | */ 121 | static reject(...args) { 122 | return Promise.reject(...args); 123 | } 124 | 125 | /** 126 | * Equivalent to `Promise.resolve`. 127 | */ 128 | static resolve(...args) { 129 | return Promise.resolved(...args); 130 | } 131 | } 132 | --------------------------------------------------------------------------------