-
Notifications
You must be signed in to change notification settings - Fork 1
Initial Bluefill implementation #1
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#### joe made this: http://goel.io/joe | ||
#### node #### | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules | ||
jspm_packages | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
*.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
* | ||
!/index.js | ||
!/index.d.ts |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
interface Promise<T> { | ||
/** | ||
* Pass a handler that will be called regardless of this promise's fate. | ||
* Returns a new promise chained from this promise. There are special | ||
* semantics for .finally in that the final value cannot be modified | ||
* from the handler. | ||
* | ||
* @see http://bluebirdjs.com/docs/api/finally.html | ||
*/ | ||
finally(handler: (result?: T, error?: Error) => any): Promise<T>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said before, this doesn't conform the bluebird implementation, that's generally fine, just add a note. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yep, wanted to fix that, forgot to |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm line |
||
|
||
/** | ||
* This is an extension to .catch to work more like catch-clauses in | ||
* languages like Java or C#. Instead of manually checking instanceof or | ||
* .name === "SomeError", you may specify a number of error constructors | ||
* which are eligible for this catch handler. The catch handler that is | ||
* first met that has eligible constructors specified, is the one that | ||
* will be called. | ||
* | ||
* @see http://bluebirdjs.com/docs/api/catch.html | ||
*/ | ||
catch<E extends Error, U>(errorCls: { new(...args: any[]): E }, onReject: (error: E) => U | PromiseLike<U>): Promise<U | T>; | ||
catch<E extends Error>(errorCls: { new(...args: any[]): E }, onReject: (error: E) => T | PromiseLike<T> | void | PromiseLike<void>): Promise<T>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about the error predicate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not supported here, though I considered it. It would require a lot more code / dependencies, the polyfill right now is quite streamlined. |
||
|
||
/** | ||
* tap is called with the result of the previously-resolved promise. It | ||
* can inspect the result and run logic, possibly returning a promise, | ||
* but the result will not be modified. | ||
*/ | ||
tap(handler: (result: T) => any): Promise<T>; | ||
|
||
/** | ||
* Takes an array of items from the previous promise, running the iterator | ||
* function over all of them with maximal concurrency. | ||
*/ | ||
map<T, R>(iterator: (item: T, index: number) => R | PromiseLike<R>): Promise<R[]>; | ||
} | ||
|
||
interface PromiseConstructor { | ||
/** | ||
* 'static' implementation of `Promise.map` | ||
*/ | ||
map<T, R>(items: T[], iterator: (item: T, index: number) => R | PromiseLike<R>): Promise<R[]>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const originalCatch = Promise.prototype.catch; | ||
|
||
Object.assign(Promise.prototype, { | ||
finally<T>(this: Promise<T>, handler: (result?: T, error?: Error) => any): Promise<T> { | ||
return this | ||
.then(result => Promise.resolve(handler(result)).then(() => result)) | ||
.catch(err => Promise.resolve(handler(undefined, err)).then(() => { throw err; })); | ||
}, | ||
|
||
catch<T>(this: Promise<T>, errorCls: Function, onReject: (error: any) => T | Promise<T>): Promise<T> { | ||
if (errorCls !== Error && !(errorCls.prototype instanceof Error)) { | ||
return originalCatch.apply(this, arguments); | ||
} | ||
|
||
return originalCatch.call(this, (err: Error) => { | ||
if (!(err instanceof errorCls)) { | ||
throw err; | ||
} | ||
|
||
return onReject(err); | ||
}); | ||
}, | ||
|
||
tap<T>(this: Promise<T>, handler: (result: T) => any): Promise<T> { | ||
return this.then(result => { | ||
handler(result); | ||
return result; | ||
}); | ||
}, | ||
|
||
map<T, R>(this: Promise<T[]>, iterator: (item: T, index: number) => R | PromiseLike<R>): Promise<R[]> { | ||
return this.then(items => { | ||
if (!Array.isArray(items)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps allow any Iterable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gave this a shot but it looks like TypeScript has issues (e.g. microsoft/TypeScript#6842) using iterators when targeting ES5 code. Thanks for the suggestion though! |
||
throw new Error('Expected array of items to Promise.map (via ' + | ||
`bluefill). Got: ${JSON.stringify(items)}`); | ||
} | ||
|
||
const promises: (R | PromiseLike<R>)[] = new Array(items.length); | ||
for (let i = 0; i < promises.length; i++) { | ||
promises[i] = iterator(items[i], i); | ||
} | ||
|
||
return Promise.all(promises); | ||
}); | ||
}, | ||
}); | ||
|
||
Object.assign(Promise, { | ||
map<T, R>(items: T[], iterator: (item: T, index: number) => R | PromiseLike<R>): Promise<R[]> { | ||
return Promise.resolve(items).map(iterator); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "bluefill", | ||
"version": "0.1.0", | ||
"description": "Bluebird-like promise polyfills with TypeScript definition support", | ||
"main": "index.js", | ||
"typings": "index.d.ts", | ||
"scripts": { | ||
"test": "npm run -s clean && mocha --compilers ts:ts-node/register test.ts", | ||
"clean": "rm -f *.js", | ||
"build": "tsc", | ||
"prepublish": "tsc" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/WatchBeam/bluefill.git" | ||
}, | ||
"keywords": [ | ||
"bluebird", | ||
"promise", | ||
"polyfill", | ||
"typescript" | ||
], | ||
"author": "Connor Peet <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/WatchBeam/bluefill/issues" | ||
}, | ||
"homepage": "https://github.com/WatchBeam/bluefill#readme", | ||
"devDependencies": { | ||
"@types/chai": "^3.5.1", | ||
"@types/chai-as-promised": "0.0.30", | ||
"@types/mocha": "^2.2.41", | ||
"chai": "^3.5.0", | ||
"chai-as-promised": "^6.0.0", | ||
"mocha": "^3.3.0", | ||
"ts-node": "^3.0.2", | ||
"typescript": "^2.2.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# bluefill | ||
|
||
Bluefill is a polyfill designed for browsers and TypeScript to add promise utility methods such as those found in [Bluebird](http://bluebirdjs.com). We do not aim for total compatibility with Bluebird or take pains to ensure extremely high performance, rather we optimize for a small browser package based solely on the Promise implementation found in the browser. | ||
|
||
The total package size of bluefill is under 400 bytes. We supply the following methods: | ||
|
||
- [`.catch`](http://bluebirdjs.com/docs/api/catch.html) with the ability to filter by Error classes as predicates. | ||
- [`.finally`](http://bluebirdjs.com/docs/api/finally.html) | ||
- [`.map`](http://bluebirdjs.com/docs/api/promise.map.html) (both as a static call and a chainable promise method) | ||
- [`.tap`](http://bluebirdjs.com/docs/api/tap.html) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { expect, use } from 'chai'; | ||
import * as chap from 'chai-as-promised'; | ||
|
||
use(chap); | ||
|
||
import './'; | ||
|
||
class FooError extends Error { | ||
constructor(message?: string) { | ||
super(message); | ||
Object.setPrototypeOf(this, FooError.prototype); | ||
} | ||
} | ||
|
||
class BarError extends Error { | ||
constructor(message?: string) { | ||
super(message); | ||
Object.setPrototypeOf(this, BarError.prototype); | ||
} | ||
} | ||
|
||
const ok = Symbol('promise result passed'); | ||
|
||
describe('Promise.catch', () => { | ||
it('does not interfere with normal catches', () => { | ||
const err = new FooError(); | ||
return expect( | ||
Promise.reject(err).catch(e => { | ||
expect(e).to.equal(err); | ||
return ok; | ||
}) | ||
).to.eventually.equal(ok); | ||
}); | ||
|
||
it('catches by class constructor', () => { | ||
const err = new FooError(); | ||
return expect( | ||
Promise.reject(err).catch(FooError, e => { | ||
expect(e).to.equal(err); | ||
return ok; | ||
}) | ||
).to.eventually.equal(ok); | ||
}); | ||
|
||
it('does not catch if the constructor is mismatched', () => { | ||
const err = new FooError(); | ||
return expect(Promise.reject(err).catch(BarError, () => ok)) | ||
.to.eventually.be.rejectedWith(err); | ||
}); | ||
|
||
it('catches if the constructor is a parent class', () => { | ||
const err = new FooError(); | ||
return expect(Promise.reject(err).catch(Error, () => ok)) | ||
.to.eventually.equal(ok); | ||
}); | ||
}); | ||
|
||
describe('Promise.finally', () => { | ||
it('runs when promise is resolved', () => { | ||
let finallyCalled = false; | ||
return expect( | ||
Promise.resolve(ok).finally(() => finallyCalled = true) | ||
) | ||
.to.eventually.equal(ok) | ||
.then(() => expect(finallyCalled).to.be.true); | ||
}); | ||
|
||
it('runs when promise is rejected', () => { | ||
let finallyCalled = false; | ||
const err = new FooError(); | ||
return expect( | ||
Promise.reject(err).finally(() => finallyCalled = true) | ||
) | ||
.to.eventually.rejectedWith(err) | ||
.then(() => expect(finallyCalled).to.be.true); | ||
}); | ||
}); | ||
|
||
describe('Promise.tap', () => { | ||
it('intercepts and does modify result', () => { | ||
let tappedResult: number; | ||
return expect( | ||
Promise.resolve(2).tap(r => { | ||
tappedResult = r; | ||
return r * 2; | ||
}) | ||
).to.eventually.equal(2); | ||
}); | ||
|
||
it('is skipped during a rejection', () => { | ||
const err = new FooError(); | ||
return expect( | ||
Promise.reject(err).tap(() => { | ||
throw new Error('should not have been called'); | ||
}) | ||
).to.eventually.be.rejectedWith(err); | ||
}); | ||
}); | ||
|
||
describe('Promise.map', () => { | ||
it('maps over items where values are returned', () => { | ||
return expect( | ||
Promise.resolve([1, 2, 3]).map((item: number) => item * 2) | ||
).to.eventually.deep.equal([2, 4, 6]); | ||
}); | ||
|
||
it('maps over items where promises are returned', () => { | ||
return expect( | ||
Promise.resolve([1, 2, 3]).map((item: number) => Promise.resolve(item * 2)) | ||
).to.eventually.deep.equal([2, 4, 6]); | ||
}); | ||
|
||
it('(static) maps over items where values are returned', () => { | ||
return expect( | ||
Promise.map([1, 2, 3], item => item * 2) | ||
).to.eventually.deep.equal([2, 4, 6]); | ||
}); | ||
|
||
it('(static) maps over items where promises are returned', () => { | ||
return expect( | ||
Promise.map([1, 2, 3], item => Promise.resolve(item * 2)) | ||
).to.eventually.deep.equal([2, 4, 6]); | ||
}); | ||
|
||
it('throws if a non-array is provided', () => { | ||
return expect( | ||
Promise.resolve('wut').map((item: number) => item * 2) | ||
).to.eventually.rejectedWith(/Expected array of items/); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"compilerOptions": { | ||
"noImplicitAny": true, | ||
"noImplicitReturns": true, | ||
"noImplicitThis": true, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": true, | ||
"strictNullChecks": true, | ||
"module": "commonjs", | ||
"target": "es5", | ||
"lib": [ | ||
"es5", | ||
"es6" | ||
], | ||
"typeRoots": [ | ||
"node_modules/@types" | ||
], | ||
"types": [ | ||
"mocha" | ||
] | ||
}, | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be overridden when
index.ts
is being build?Can we please stick to having a
src
and adist
folder? 😄There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, I told tsc not to emite declarations.