Skip to content

Instantly share code, notes, and snippets.

@elado
Last active November 5, 2015 06:26
Show Gist options
  • Save elado/c493a56925bfdeadc429 to your computer and use it in GitHub Desktop.
Save elado/c493a56925bfdeadc429 to your computer and use it in GitHub Desktop.
reusePromise
const pendingPromisesMap = () => {
const FN_INDEX = 0
const KEY_INDEX = 1
const VALUE_INDEX = 2
const a = []
function is(item, fn, key) {
if (item[FN_INDEX] == fn && item[KEY_INDEX] == key) {
return true
}
return false
}
function getIndex(fn, key) {
for (let i = 0, l = a.length; i < l; i++) {
const item = a[i]
if (is(item, fn, key)) {
return i
}
}
return -1
}
function get(fn, key) {
const i = getIndex(fn, key)
if (a[i]) {
return a[i][VALUE_INDEX]
}
return null
}
function set(fn, key, value) {
const i = getIndex(fn, key)
if (i > -1) {
a[i][VALUE_INDEX] = value
}
else {
const obj = []
obj[FN_INDEX] = fn
obj[KEY_INDEX] = key
obj[VALUE_INDEX] = value
a.push(obj)
}
return value
}
function del(fn, key) {
const i = getIndex(fn, key)
if (i > -1) {
a.splice(i, 1)
}
}
function clear() {
a.length = 0
}
return {
get,
set,
del,
clear
}
}()
export default function reusePromise(target, name, descriptor) {
let getter = descriptor.value
descriptor.value = function () {
const key = JSON.stringify(arguments)
const pendingPromise = pendingPromisesMap.get(getter, key)
if (pendingPromise) {
return pendingPromise
}
const forgetPromise = () => {
pendingPromisesMap.del(getter, key)
}
let promise = getter.apply(this, arguments).then((value) => {
forgetPromise()
return value
}, (err) => {
forgetPromise()
throw err
})
pendingPromisesMap.set(getter, key, promise)
return promise
}
return descriptor
}
reusePromise.clear = pendingPromisesMap.clear
import assert from 'assert'
import reusePromise from './reusePromise'
describe('reusePromise', function () {
class Test {
@reusePromise
find(id) {
return new Promise((fulfil, reject) => {
setTimeout(() => {
fulfil({ id: id })
}, 5)
})
}
@reusePromise
findWithError(id) {
return new Promise((fulfil, reject) => {
setTimeout(() => {
reject(new Error)
}, 5)
})
}
}
let test
beforeEach(function () {
test = new Test()
})
it('reuses the same promise if the current one is still pending', function () {
const p1 = test.find(1)
assert.equal(p1, test.find(1))
const p2 = test.find(2)
assert.equal(p2, test.find(2))
assert.notEqual(p1, test.find(2))
})
it('asks for a new promise if previous one was fulfilled', function (done) {
const p1 = test.find(1)
assert.equal(p1, test.find(1))
setTimeout(() => {
assert.equal(p1, test.find(1))
setTimeout(() => {
assert.notEqual(p1, test.find(1))
done()
}, 7)
}, 2)
})
it('fullfils the promise once with same value', function (done) {
const promises = [test.find(1), test.find(1), test.find(1)]
const values = []
let pending = promises.length
promises.forEach((p) => {
p.then((v) => {
values.push(v)
check()
})
})
function check() {
pending--
if (pending == 0) {
for (let i = 0; i < values.length - 1; i++) {
assert.equal(values[i], values[i + 1])
}
done()
}
}
})
it('rejects the promise once with same error', function (done) {
const promises = [test.findWithError(1), test.findWithError(1), test.findWithError(1)]
const errors = []
let pending = promises.length
promises.forEach((p) => {
p.then(() => {
assert.fail()
}, (err) => {
errors.push(err)
check()
})
})
function check() {
pending--
if (pending == 0) {
for (let i = 0; i < errors.length - 1; i++) {
assert.equal(errors[i], errors[i + 1])
}
done()
}
}
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment