I have a method inside a class calculating the sum of 2 parameters that returns a Promise (a must):
module.exports = class Sum {
sum(a, b) {
let c = a + b;
const myPromise = new Promise(function(myResolve) {
setTimeout(function(){ myResolve(c); }, 100);
});
return myPromise;
}
}
I use Jasmine framework to unit-test
const MyLocalVariable1 = require('../src/sum');
describe('CommonJS modules', () => {
it('should import whole module using module.exports = sum;', async () => {
const result = new MyLocalVariable1();
expect(result.sum(4, 5).then(function(value) {
return value;
})).toBe(9);
});
}
The first expression is what we want to test:
result.sum(4, 5).then(function(value) {
return value;
})
and the second one is the expected value:
toBe(9)
But how can I get a value from the first expression, because it's a Promise, and expected value of it is [object Promise]. Thanks in advance
To drive Konrad's point home, you can do the following:
it('should import whole module using module.exports = sum;', async () => {
const result = new MyLocalVariable1();
const answer = await result.sum(4, 5);
expect(answer).toBe(9);
});
Or the following:
// add done callback to tell Jasmine when you're done with the unit test
it('should import whole module using module.exports = sum;', (done) => {
const result = new MyLocalVariable1();
result.sum(4, 5).then(answer => {
expect(answer).toBe(9);
done();
});
});
I am trying to write mock a test for the following function call
const contract = await new web3.eth.Contract();
tx.data = await contract.methods
.setup(
[juryAddress, buyerAddress, sellerAddress],
threshold,
to,
txData,
fallbackHandler,
paymentToken,
0,
sellerAddress
)
.encodeABI();
I am trying to mock this as a function contract.methods.setup() that returns a function object encodeABI() which then returns a dummy value {}.
The mock I am trying looks like this, although it is not working
const encodeABI = jest.fn()
encodeABI.mockReturnValue({})
const contract = {
methods: {
setup: jest.fn(),
}
}
eth.Contract.mockResolvedValue(contract)
contract.methods.setup.mockResolvedValue(encodeABI)
expect(encodeABI).toBeCalled()
expect(encodeABI).toBeCalled() is not being called as I expect
Your production code awaits encodeABI(), not setup(). It expects setup() to return an object with an encodeABI() function, not a Promise.
I would recommend making the following changes
const encodeABI = jest.fn(async () => ({})) // return a Promise here
const contract = {
methods: {
setup: jest.fn(() => ({ encodeABI })) // return an object with encodeABI
}
}
I have the following code:
class simplePromise {
constructor(resolveFn, rejectFn) {
console.log(resolveFn, 'resolveFn') // (resolutionFunc, rejectionFunc) => {
// resolutionFunc(777);
// rejectionFunc();
// }
console.log(rejectFn, 'rejectFn') //undefined
}
}
const promise1 = new simplePromise( (resolutionFunc, rejectionFunc) => {
resolutionFunc(777);
rejectionFunc();
});
As you can see, I'm trying to pass in 2 functions to the constructor. However, when I console.logged each of them out, I noticed that both functions are registered as 1 argument. In this case, how do I separate the 2 functions?
Are you sure that you want to pass 2 functions to the constructor?
const promise1 = new simplePromise( (resolutionFunc, rejectionFunc) => {...}
this part of your code looks more like you want a single callback function that recieves 2 functions.
Like this:
class SimplePromise {
constructor(callback) {
const resolveFn = (value) => {
console.log("resolved with value", value);
};
const rejectFn = (error) => {
console.log("rejected with error", error);
}
callback(resolveFn, rejectFn);
}
}
const promise1 = new SimplePromise((resolutionFunc, rejectionFunc) => {
console.log('resolutionFunc', resolutionFunc)
console.log('rejectionFunc', rejectionFunc) //undefined
resolutionFunc(777);
rejectionFunc();
});
Not that this code has anything to do with a Promise, but you should study the following structure:
class simplePromise{
constructor(resolveFn, rejectFn){
this.prop1 = 'prop1value'; this.prop2 = 'prop2value';
resolveFn.call(this, 'Passing to first function argument'); // calling in simplePromise context
rejectFn.call(this, 'Passing to second function argument');
}
}
const promise1 = new simplePromise(function(funcOneArg){
console.log(funcOneArg);;
console.log("this.prop1 = '"+this.prop1+"';"); // see why `.call(this` is in `constructor`
console.log("this.prop2 = '"+this.prop2+"';");
},
function(functionTwoArg){
console.log('-'.repeat(45));
console.log(functionTwoArg);
});
So when you pass the arrow function to the new simplePromise constructor, that entire function is the first argument (resolveFn). In order to instantiate this the way you want, you need to define the resolutionFunc and rejectionFunc and then do it like so:
const resolutionFunc = () => {do stuff}
const rejectionFunc = () => {do stuff}
const promise1 = new simplePromise(resolutionFunc, rejectionFunc)
How can I achieve dynamic callback arguments in JavaScript
I have a three functions I want to compose. Why am I doing this is because I want to encapsulate the details of the initDB so I can write less code. Here's what it looks like below:
const initDB = (schemas: any[]) =>
Realm.open({ path: 'CircleYY.realm', schema: schemas })
.then((realm: Realm) => ({ realm }))
.catch((error: Error) => ({ error }));
So basically this function just initialize a DB and it will return a DB instance or an Error.
I also have some specific database write functions like this below:
// Delete a message
const deleteOrder = (orderID: string, realm: Realm) => {
realm.write(() => {
const order = realm.objects('Orders').filtered(`primaryKey = ${id}`);
realm.delete(order);
});
};
and I have this three functions below:
makeDBTransaction(deleteOrder(id));
and
makeDBTransaction(writeCommentInOrder(orderId, comment))
and
const makeDBTransaction = async (callback: Function) => {
const { error, realm } = (await initDB([
OrderSchema,
ProductSchema,
])) as InitRealm;
if (error) return { error };
callback(realm); // Pass realm while maintaining the arguments specified in the callback which is dynamic
return realm.close();
};
I also want to pass the realm into the callback that can have more than 2 arguments.
How can I achieve that?
I think you can keep adding arguments in an array in a required order and then apply the arguments to the function and call that function.
//for example
function foo1(x, y) {
console.log(x,y);
}
function foo2(cb, y) {
const x = 3;
cb.apply(null, [x,y]);
}
foo2(foo1, 5);
//So your code will be like this
makeDBTransaction(deleteOrder, [id]);
const makeDBTransaction = async (callback: Function, arg: any[]) => {
const { error, realm } = (await initDB([
OrderSchema,
ProductSchema,
])) as InitRealm;
if (error) return { error };
arg.push(realm);
callback.apply(null, arg);
return realm.close();
};
Background
I am trying to filter an array of objects. Before I filter, I need to convert them to some format, and this operation is asynchronous.
const convert = () => new Promise( resolve => {
setTimeout( resolve, 1000 );
});
So, my first try was to do something like the following using async/await:
const objs = [ { id: 1, data: "hello" }, { id: 2, data: "world"} ];
objs.filter( async ( obj ) => {
await convert();
return obj.data === "hello";
});
Now, as some of you may know, Array.protoype.filter is a function which callback must return either true or false. filter is synchronous. In the previous example, I am returning none of them, I return a Promise ( all async functions are Promises ).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
So as one can assume, the code before doesn't really work... That assumption is correct.
Problem
To make filter work with an async function, I checked stackoverflow and found this topic:
Filtering an array with a function that returns a promise
Unfortunately, the chosen answer is overly complex and uses classes. This won't do for me. I am instead looking for a more simple solution, using simple functions with a functional approach.
There is one solution at the very end, using a map with a callback to simulate a filter:
https://stackoverflow.com/a/46842181/1337392
But I was hoping to fix my filter function, not to replace it.
Questions
Is there a way to have an async function inside a filter?
If not, what is the simplest replacement I can do?
There is no way to use filter with an async function (at least that I know of).
The simplest way that you have to use filter with a collection of promises is to use Promise.all and then apply the function to your collection of results.
It would look something like this:
const results = await Promise.all(your_promises)
const filtered_results = results.filter(res => //do your filtering here)
Hope it helps.
Adapted from the article How to use async functions with Array.filter in Javascript by Tamás Sallai, you basically have 2 steps:
One that creates the conditions for an object to pass
One that receives the objects and returns true or false according to conditions
Here's an example
const arr = [1, 2, 3, 4, 5];
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const asyncFilter = async (arr, predicate) => {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}
const asyncRes = await asyncFilter(arr, async (i) => {
await sleep(10);
return i % 2 === 0;
});
console.log(asyncRes);
// 2,4
Use Scramjet fromArray/toArray methods...
const result = await scramjet.fromArray(arr)
.filter(async (item) => somePromiseReturningMethod(item))
.toArray();
as simple as that - here's a ready example to copy/paste:
const scramjet = require('../../');
async function myAsyncFilterFunc(data) {
return new Promise(res => {
process.nextTick(res.bind(null, data % 2));
});
}
async function x() {
const x = await scramjet.fromArray([1,2,3,4,5])
.filter(async (item) => myAsyncFilterFunc(item))
.toArray();
return x;
}
x().then(
(out) => console.log(out),
(err) => (console.error(err), process.exit(3)) // eslint-disable-line
);
Disclamer: I am the author of scramjet. :)
Build a parallel array to your array which you want to call filter on.
Await all of the promises from your filter func, in my eg, isValid.
In the callback in filter, use the 2nd arg, index, to index into your parallel array to determine if it should be filtered.
// ===============================================
// common
// ===============================================
const isValid = async (value) => value >= 0.5;
const values = [0.2, 0.3, 0.4, 0.5, 0.6];
// ===============================================
// won't filter anything
// ===============================================
const filtered = values.filter(async v => await isValid(v));
console.log(JSON.stringify(filtered));
// ===============================================
// filters
// ===============================================
(async () => {
const shouldFilter = await Promise.all(values.map(isValid));
const filtered2 = values.filter((value, index) => shouldFilter[index]);
console.log(JSON.stringify(filtered2));
})();
This behavior makes sense since any Promise instance has a truthy value, but it's not intuitive at a glance.
This answer uses library iter-ops, which handles iterable objects, and supports async filtering:
import {pipeAsync, filter, toAsync} from 'iter-ops';
// your input data:
const objs = [{id: 1, data: 'hello'}, {id: 2, data: 'world'}];
const i = pipeAsync(
objs,
filter(async value => {
await convert(); // any async function
return value.data === 'hello'; // filtering logic
})
);
for await(const a of i) {
console.log(a); // filtered data
}
P.S. I'm the author of iter-ops.
Reduce method can mimic filter and can operate with promises.
const isPositiveNumberAsync = async (number) => number >= 0;
const filterPositiveNumbersAsync = async (numbers) => numbers?.reduce(async (accumulatorPromise, number) => {
const accumulator = await accumulatorPromise;
if (await isPositiveNumberAsync(number)) {
return [...accumulator, number];
}
return accumulator;
}, Promise.resolve([])) || [];
(async () => {
// no numbers argument provided
console.log(await filterPositiveNumbersAsync());
// an empty argument list provided
console.log(await filterPositiveNumbersAsync([]));
// ok, but no positive numbers provided
console.log(await filterPositiveNumbersAsync([-1,-2,-3]));
// ok, positive numbers filtered.
console.log(await filterPositiveNumbersAsync([0,1,-1,-3,2,-2]));
})();
Array.prototype.asyncFilter =function( filterFn) {
const arr = this;
return new Promise(function(resolve){
const booleanArr = [];
arr.forEach(function (e) {
booleanArr.push(filterFn(e))
})
Promise.all(booleanArr).then(function (booleanArr) {
const arr2 = arr.filter(function (e, i) {
return booleanArr[i]
})
resolve(arr2)
})
})
}
/** use it like this**/
const arr=[1,2,3]
arr.asyncFilter(async e=>{}).then(...)
You can use Promise.filter from Bluebird that works similarly to Array.filter but it supports async & await.
Add asyncFilter as an extension to Array:
#available(macOS 10.15.0, *)
extension Array where Element: Any {
public func asyncFilter(closure: (Element) async -> Bool) async -> Array {
var result = [Element]()
for item in self {
if await closure(item) {
result.append(item)
}
}
return result
}
}
Usage:
result = await result.asyncFilter { item in
if <item match> {
return true
}
}