Can I resolve a promise when a variable change without using deferr? - javascript

I am converting some Q-promise based typescript code to ES6-promises.
At a certain point, I used Q.defer and in my migration I just rewritten defer as an ES6 promise like explained in this comment:
https://stackoverflow.com/a/49825137/13116953
I was trying to get rid of this defer approach, if possible, and was looking for alternative solutions.
Reasons of my question are:
Deferred promises are considered an anti-pattern, in general
Want to know if this scenario is one of the few where deferred are really the only way
Here's my scenario:
// app start, this is my low level API
init() {
commService.subscribe("myRecordId", {
onRecordAdd: this.onAdd
});
}
...
private _myRecord: MyRecordObj | null = null;
private _recordReceivedDef = newDeferred(); // was Q.defer()
// callback will be called when record is received
private readonly onAdd = (recordObj: MyRecordObj) => {
this._myRecord = recordObj;
this._recordReceivedDef.resolve(recordObj);
}
...
// here's my async code that requires the deferred
public readonly doStuff = async () => {
// ...
const myRec = await this._recordReceivedDef.promise;
// use myRef
}
My question is: is there a way I can get rid of this defer?
I was thinking of something that resolves when _myRecord changes, but have no idea how to do it.
Side note:
I use MobX in other parts of our app, thus having
await when(() => this._myRecord); // of course _myRecord must be #observable
would be handy, but unfortunately I cannot use MobX in this particular piece of code.
Any help is much appreciated.
Thanks a lot!

Assuming init is called before doStuff, the proper way would be
init() {
this._myRecordPromise = new Promise((resolve, reject) => {
commService.subscribe("myRecordId", {
onRecordAdd: (recordObj: MyRecordObj) => {
// callback will be called when record is received
resolve(this._myRecord = recordObj);
}
});
});
}
…
private _myRecord: MyRecordObj | null = null;
private _myRecordPromise: Promise<MyRecordObj>;
…
public readonly doStuff = async () => {
…
const myRec = await this._myRecordPromise;
// use myRef
}
You might even drop the _myRecord completely and keep only the _myRecordPromise.
However, you might want to consider not constructing your instance at all before the record is received, see Is it bad practice to have a constructor function return a Promise?.
If init is called at some arbitrary time, you will need some kind of defer pattern, but you don't need newDeferred() for that. Just write
init() {
commService.subscribe("myRecordId", {
onRecordAdd: this.onAdd
});
}
…
private _myRecordPromise: Promise<MyRecordObj> = new Promise(resolve => {
this.onAdd = resolve;
});
private readonly onAdd: (recordObj: MyRecordObj) => void;

For people who might be interested, there is also another solution that uses an event emitter approach.
Suppose you have a class EventEmitter that implements the following interface:
// pseudo code
type UnsubscribeFn = () => void;
interface IEventEmitter<T> {
/** Fires an event */
emit(args: T): void;
/** Subscribes to emissions of this event and provides an unsubscribe function */
subscribe((args: T) => void): UnsubscribeFn;
}
you can provide your clients with a whenAdded function like below
// pseudo code
class Subscriber {
readonly onAddEmitter = new EventEmitter<MyRecordObj>();
// app start
init() {
commService.subscribe("myRecordId", {
onRecordAdd: this.onAdd
});
}
onAdd = (rec: MyRecordObj) => {
// do some stuff
onAddEmitter.emit(rec);
}
whenAdded(): Promise<MyRecordObj> {
return new Promise((res) => {
const unsub = onAddEmitter.subscribe((record: MyRecordObj) => {
unsub();
res(record);
});
});
}
}
The goals achieved are:
Get rid of Q promises in favour of ES6 ones
Adapt the commService event API to a promise-based one

Related

How to stub function that returns a promise?

I'm trying to stub a function using sinon. The function has the following signature
export function getIndexDocument(
svc: MetaHTTPService | ServiceConfig
): MetaPromise<RepoResponseResult<IndexDocument>> {
Is this the right way to sub it
sandbox.stub(getIndexDocument).resolves({} as RepoResponseResult)
I tried that but it returns an error.
Here's how this function is called.
I have a class called AssetsController with the following functions
public async exploreIndexDocument(): Promise<Asset | undefined> {
// it makes an HTTP request and returns a promise that resolves with the following info { repoId: "", assetId: "" }
const {
result: { assignedDirectories }
} = await getIndexDocument(this.serviceConfig).catch(err => {
throw new Error(`Bad repsonse`);
});
return {
repoId: result.repoId;
assetId: result.assetId
}
}
public async function copyAsset(asset) {
const res = await this.exploreIndexDocument();
const repoId = res.repoId;
return asset.copy(repoId);
}
I'm trying to test the function copyAsset, but it calls exploreIndexDocument which calls getIndexDocument. getIndexDocument is imported at the top of the file and lives in the module #ma/http.
getIndexDocument makes an HTTP request.
How can I test copyAsset given that it calls getIndexDocument which makes an HTTP request?
According to the docs, you can't stub an existing function.
You can:
// Create an anonymous sstub function
var stub = sinon.stub();
// Replaces object.method with a stub function. An exception is thrown
// if the property is not already a function.
var stub = sinon.stub(object, "method");
// Stubs all the object’s methods.
var stub = sinon.stub(obj);
What you can't do is stub just a function like:
var stub = sinon.stub(myFunctionHere);
This makes sense because if all you have is a reference to a function, then you can just create a new function to use instead, and then pass that into where ever your test needs it to go.
I think you just want:
const myStub = sandbox.stub().resolves({} as RepoResponseResult)
In your update it sounds like you want to put the stub on the AssetsController class. See this answer for more info on that, but in this case I think you want:
const myStub = sandbox
.stub(AssetsController.prototype, 'exploreIndexDocument')
.resolves({} as RepoResponseResult)
Now anytime an instance of AssetsController calls its exploreIndexDocument method, the stub should be used instead.
Playground
I think most of your problems can be solved by revisiting your architecture. For example, instead of creating an explicit dependency on getIndexDocument within your AssetController class you can simply inject it in. This will allow you to swap implementations depending on the context.
type IndexDocumentProvider = (svc: MetaHTTPService | ServiceConfig) => MetaPromise<RepoResponseResult<IndexDocument>>;
interface AssetControllerOptions {
indexDocumentProvider: IndexDocumentProvider
}
class AssetController {
private _getIndexDocument: IndexDocumentProvider;
public constructor(options: AssetControllerOptions) {
this._getIndexDocument = options.indexDocumentProvider;
}
}
Then you can use this._getIndexDocument wherever and not worry about how to make the original implementation behave like you want in your tests. You can simply provide an implementation that does whatever you'd like.
describe('copyAsset', () => {
it('fails on index document error.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.reject(new Error(':('));
});
....
});
it('copies asset using repo id.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.resolve({ repoId: "420" })
});
...
});
});
You can obviously use stubs instead of just functions or whatever if you need something fancy.
Above we removed an explicit dependency to an implementation and instead replaced it with a contract that must be provided to the controller. The is typically called Inversion of Control and Dependency Injection

Subclassing in Javascript

I'm trying to make a subclass of an image library on github called Jimp. As far as I can tell from the docs, you don't instantiate the class in the usual way. Instead of saying new Jimp(), it seems the class has a static method called read that acts as a constructor. From the docs...
Jimp.read("./path/to/image.jpg").then(function (image) {
// do stuff with the image
}).catch(function (err) {
// handle an exception
});
It looks like from the docs, that that image returned by read() is an instance allowing the caller to do stuff like image.resize( w, h[, mode] ); and so on.
I'd like to allow my subclass callers to begin with a different static method that reads an image and does a bunch of stuff, summarized as follows...
class MyJimpSubclass extends Jimp {
static makeAnImageAndDoSomeStuff(params) {
let image = null;
// read in a blank image and change it
return Jimp.read("./lib/base.png").then(_image => {
console.log(`image is ${_image}`);
image = _image;
let foo = image.bar(); // PROBLEM!
// ...
// ...
.then(() => image);
}
bar() {
// an instance method I wish to add to the subclass
}
// caller
MyJimpSubclass.makeAnImageAndDoSomeStuff(params).then(image => {
//...
});
You might be able to guess that nodejs gets angry on the line let foo = image.bar();, saying
TypeError image.bar is not a function
.
I think this is understandable, because I got that image using Jimp.read(). Of course that won't return an instance of my subclass.
First idea: Change it to MyJimpSubclass.read(). Same problem.
Second idea: Implement my own static read method. Same problem.
static read(params) {
return super.read(params);
}
Third idea: Ask SO
The implementation of Jimp.read refers to Jimp specifically, so you would have to copy and change it in your subclass (ick, but not going to break anything since the constructor is also part of the API) or make a pull request to have it changed to this and have subclassing explicitly supported:
static read(src) {
return new Promise((resolve, reject) => {
void new this(src, (err, image) => {
if (err) reject(err);
else resolve(image);
});
});
}
Alternatively, you could just implement all your functionality as a set of functions on a module. This would be next on my list after making a pull request. Would not recommend a proxy.
const makeAnImageAndDoSomeStuff = (params) =>
Jimp.read("./lib/base.png").then(image => {
console.log(`image is ${image}`);
let foo = bar(image);
// …
return image;
});
function bar(image) {
// …
}
module.exports = {
makeAnImageAndDoSomeStuff,
bar,
};
Even changing the prototype would be better than a proxy (but this is just a worse version of the first option, reimplementing read):
static read(src) {
return super.read(src)
.then(image => {
Object.setPrototypeOf(image, this.prototype);
return image;
});
}
You have a couple of options. The cleanest is probably to make a subclass like you started, but then implement the Jimp static method on it, as well as your own. In this case, it's not really inheritance, so don't use extends.
class MyJimp {
static read(...args) {
return Jimp.read.apply(Jimp, args);
}
static makeAnImage(params) {
return this.read(params)
.then(image => {
// do stuff
return image
});
}
}
From there, I would make an object which has all of the new functions you want to apply to image:
const JimpImageExtension = {
bar: () => { /* do something */ }
};
Finally, in your static methods, get the image and use Object.assign() to apply your new functions to it:
class MyJimp {
static read(...args) {
return Jimp.read.apply(Jimp, args)
.then(image => Object.assign(image, JimpImageExtension));
}
static makeAnImage(params) {
return this.read(params)
.then(image => {
// do stuff
image.bar();
return image;
});
}
}
This should do the trick by applying your extra functions to the image. You just need to make sure that you apply it at every point that can generate an image (if there is more than just read). Since in the other functions, it's using your version of read(), you only need to add the functions in the one.
Another approach would be if Jimp makes their image class accessible, you could also add them to the prototype of that (though usually in libraries like this, that class is frequently inaccessible or not actually a class at all).
This might be a way to do it. Start with your own read method, and have it change the prototype of the returned object.
static read(...params) {
return super.read(...params).then(image) {
image.prototype = MyJimpSubclass;
resolve(image);
}
}

Mock Es6 classes using Jest

I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.

Extend RxJS Observable class with operators

How can Observable class be extended by applying built-in RxJS operators to it?
I would like to do something like this:
class TruthyObservable extends Observable {
constructor(subscriber) {
super(subscriber);
return this.filter(x => x);
}
}
class TruthyMappedObservable extends TruthyObservable {
constructor(subscriber) {
super(subscriber);
return this.map(x => `'${x}'`);
}
}
Can this be done without constructor return?
This pretty much depends on what you want to do but let's say you want to make a TruthyObservable that behaves very much like the default Observable.create(...) but passes only even numbers:
import { Observable, Observer, Subscriber, Subject, Subscription } from 'rxjs';
import 'rxjs/add/operator/filter';
class TruthyObservable<T> extends Observable<T> {
constructor(subscribe?: <R>(this: Observable<T>, subscriber: Subscriber<R>) => any) {
if (subscribe) {
let oldSubscribe = subscribe;
subscribe = (obs: Subscriber<any>) => {
obs = this.appendOperators(obs);
return oldSubscribe.call(this, obs);
};
}
super(subscribe);
}
private appendOperators(obs: Subscriber<any>) {
let subject = new Subject();
subject
.filter((val: number) => val % 2 == 0)
.subscribe(obs);
return new Subscriber(subject);
}
}
let o = new TruthyObservable<number>((obs: Observer<number>) => {
obs.next(3);
obs.next(6);
obs.next(7);
obs.next(8);
});
o.subscribe(val => console.log(val));
This prints to console:
6
8
See live demo: https://jsbin.com/recuto/3/edit?js,console
Usually classes inheriting Observable override the _subscribe() method that actually makes the subscription internally but in ours case we want to use the callback where we can emit values by ourselves (since this Observable doesn't emit anything itself). Method _subscribe() is overshadowed by _subscribe property if it exists so we wouldn't be able to append any operators to it if we just overrode this method. That's why I wrap _subscribe in the constructor with another function and then pass all values through a Subject chained with filter() in appendOperators() method. Note that I replaced the original Observer with the Subject at obs = this.appendOperators(obs).
At the end when I call eg. obs.next(3); I'm in fact pushing values to the Subject that filters them and passes them to the original Observer.
I think you can get what you need with custom operator:
Observable.prototype.truthy = function truthy() {
return this.filter(x => x);
}

Typescript derived class as callback signature

I'm trying to make a base class that issues a method for throtteling highly frequented event calls like the document.onscroll event. Here is my base class:
class ThrottledRunner {
private timerId: number;
lastTimeRun: number;
runAtMostEvery = 100;
// Here is the Method
runThrottled(action: (e: ThrottledRunner) => void) {
var now: number = new Date().getTime();
if (this.timerId == null) {
if (now - this.lastTimeRun > (3 * this.runAtMostEvery)) {
action(this);
this.lastTimeRun = now;
}
this.timerId = setTimeout(function (e: ThrottledRunner) {
e.timerId = null;
e.lastTimeRun = new Date().getTime();
action(e);
}, this.runAtMostEvery);
}
}
}
My derived class:
class MyTest extends ThrottledRunner {
myProp: string = "works";
constructor() {
super();
window.addEventListener("scroll", () => this.runThrottled(this.onScroll(this)));
// Supplied parameters do not match any signature of call target.
// Could not select overload for 'call' expression.
}
onScroll(self: MyTest): void {
alert(self.myProp);
}
}
Since MyTest derives from ThrottledRunner, runThrottled() should accept it as a parmeter but it seems i am wrong. I moved completely to Typescript + vanillajs, so no jQuery answers please.
Have you had a look at using underscorejs throttle() function ?
_.throttle(function, wait, [options])
Creates and returns a new, throttled version of the passed function, that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds. Useful for rate-limiting events that occur faster than you can keep up with.
Underscore has a number of extremely useful functions, and has full TypeScript and nuGet support : underscore.TypeScript.DefinitelyTyped
You can't call onScroll the way you are as it's calling it immediately upon executing, when it really needs to wait until your runThrottled application is ready. I've changed the onScroll method to not need a parameter as the this context is set correctly.
If you change your class to:
class MyTest extends ThrottledRunner {
myProp: string = "works";
constructor() {
super();
window.addEventListener("scroll",
() => this.runThrottled(() => this.onScroll()));
}
onScroll(): void {
console.log(this.myProp);
}
}
The this will be correct within the context of the runThrottled.

Categories