JS Promise in vue shows as undefined - javascript

I have a promise that return once a correct event is called with the correct action. This is what I have so far
import {EventBus} from "./EventBus";
export function completed() {
EventBus.$on('queue-action', e => {
return new Promise((resolve,reject) => {
if(e.action == 'completed'){
let item = e.queueItem
resolve(item);
}else{
reject(new Error('No action specified in event object'))
}
})
});
}
export function emitAction(action, queueItem) {
EventBus.$emit('queue-action', {
action,
queueItem
});
}
When calling the completed function in one of my components like this
completed()
.then((item)=> console.log('promise'))
.catch((error) => console.log(error) );
it returns undefined once I add the then and catch methods to this function. It looks like the problem is with me then and catch, but I am unable to determine what it is. From what I have seen online whatever variable you use for the data you use in the then statement.
What I am trying to do is let an element in the "queue" to emit an event to the to the queue with an action for example completed. The queue should then resolve the promise to edit the queue in the intended purpose of that action or react to an error from the promise.
This is what I have done so far
import {EventBus} from "./EventBus";
export class QueueEvent {
constructor(){}
emitAction(action, queueItem){
return new Promise((resolve,reject) => {
EventBus.$emit('queue-action', {
action,
queueItem
},resolve,reject);
});
}
}
export class QueueEvents extends QueueEvent{
constructor(){
super();
}
listenForComplete() {
}
}

Your completed function is not returning a promise (it is returning undefined as you noticed).
You are returning the promise for the event emitter when the queue-action is called. You are defining a new function here: e => { and that function that is returning a promise is passed to the EventBus event emitter

You want to wrap the whole EventBus.$on() in your promise, like this:
export function completed() {
return new Promise((resolve) => {
EventBus.$on('queue-action', e => {
if(e.action == 'completed'){
let item = e.queueItem
resolve(item);
}
});
});
}
As a rule of thumb, unless you have a very specific reason to do something else, a function returning a promise should have all it's body wrapped in return new Promise(...);. It is also normal and ok to have a lot of code wrapped inside a promise.
Note to the code: I removed reject part both for brevity and because I'm not sure that is what you want to do. Unless it is an error if some action happens before 'completed', you should just ignore such an event.

Related

How to use the AbortController to cancel Promises in React?

I want to cancel a promise in my React application using the AbortController and unfortunately the abort event is not recognized so that I cannot react to it.
My setup looks like this:
WrapperComponent.tsx: Here I'm creating the AbortController and pass the signal to my method calculateSomeStuff that returns a Promise. The controller I'm passing to my Table component as a prop.
export const WrapperComponent = () => {
const controller = new AbortController();
const signal = abortController.signal;
// This function gets called in my useEffect
// I'm passing signal to the method calculateSomeStuff
const doSomeStuff = (file: any): void => {
calculateSomeStuff(signal, file)
.then((hash) => {
// do some stuff
})
.catch((error) => {
// throw error
});
};
return (<Table controller={controller} />)
}
The calculateSomeStuff method looks like this:
export const calculateSomeStuff = async (signal, file): Promise<any> => {
if (signal.aborted) {
console.log('signal.aborted', signal.aborted);
return Promise.reject(new DOMException('Aborted', 'AbortError'));
}
for (let i = 0; i <= 10; i++) {
// do some stuff
}
const secret = 'ojefbgwovwevwrf';
return new Promise((resolve, reject) => {
console.log('Promise Started');
resolve(secret);
signal.addEventListener('abort', () => {
console.log('Aborted');
reject(new DOMException('Aborted', 'AbortError'));
});
});
};
Within my Table component I call the abort() method like this:
export const Table = ({controller}) => {
const handleAbort = ( fileName: string) => {
controller.abort();
};
return (
<Button
onClick={() => handleAbort()}
/>
);
}
What am I doing wrong here? My console.logs are not visible and the signal is never set to true after calling the handleAbort handler.
Based off your code, there are a few corrections to make:
Don't return new Promise() inside an async function
You use new Promise if you're taking something event-based but naturally asynchronous, and wrap it into a Promise. Examples:
setTimeout
Web Worker messages
FileReader events
But in an async function, your return value will already be converted to a promise. Rejections will automatically be converted to exceptions you can catch with try/catch. Example:
async function MyAsyncFunction(): Promise<number> {
try {
const value1 = await functionThatReturnsPromise(); // unwraps promise
const value2 = await anotherPromiseReturner(); // unwraps promise
if (problem)
throw new Error('I throw, caller gets a promise that is eventually rejected')
return value1 + value2; // I return a value, caller gets a promise that is eventually resolved
} catch(e) {
// rejected promise and other errors caught here
console.error(e);
throw e; // rethrow to caller
}
}
The caller will get a promise right away, but it won't be resolved until the code hits the return statement or a throw.
What if you have work that needs to be wrapped with a Promise constructor, and you want to do it from an async function? Put the Promise constructor in a separate, non-async function. Then await the non-async function from the async function.
function wrapSomeApi() {
return new Promise(...);
}
async function myAsyncFunction() {
await wrapSomeApi();
}
When using new Promise(...), the promise must be returned before the work is done
Your code should roughly follow this pattern:
function MyAsyncWrapper() {
return new Promise((resolve, reject) => {
const workDoer = new WorkDoer();
workDoer.on('done', result => resolve(result));
workDoer.on('error', error => reject(error));
// exits right away while work completes in background
})
}
You almost never want to use Promise.resolve(value) or Promise.reject(error). Those are only for cases where you have an interface that needs a promise but you already have the value.
AbortController is for fetch only
The folks that run TC39 have been trying to figure out cancellation for a while, but right now there's no official cancellation API.
AbortController is accepted by fetch for cancelling HTTP requests, and that is useful. But it's not meant for cancelling regular old work.
Luckily, you can do it yourself. Everything with async/await is a co-routine, there's no pre-emptive multitasking where you can abort a thread or force a rejection. Instead, you can create a simple token object and pass it to your long running async function:
const token = { cancelled: false };
await doLongRunningTask(params, token);
To do the cancellation, just change the value of cancelled.
someElement.on('click', () => token.cancelled = true);
Long running work usually involves some kind of loop. Just check the token in the loop, and exit the loop if it's cancelled
async function doLongRunningTask(params: string, token: { cancelled: boolean }) {
for (const task of workToDo()) {
if (token.cancelled)
throw new Error('task got cancelled');
await task.doStep();
}
}
Since you're using react, you need token to be the same reference between renders. So, you can use the useRef hook for this:
function useCancelToken() {
const token = useRef({ cancelled: false });
const cancel = () => token.current.cancelled = true;
return [token.current, cancel];
}
const [token, cancel] = useCancelToken();
// ...
return <>
<button onClick={ () => doLongRunningTask(token) }>Start work</button>
<button onClick={ () => cancel() }>Cancel</button>
</>;
hash-wasm is only semi-async
You mentioned you were using hash-wasm. This library looks async, as all its APIs return promises. But in reality, it's only await-ing on the WASM loader. That gets cached after the first run, and after that all the calculations are synchronous.
Async code that doesn't actually await doesn't have any benefits. It will not pause to unblock the thread.
So how can you let your code breath if you've got CPU intensive code like what hash-wasm uses? You can do your work in increments, and schedule those increments with setTimeout:
for (const step of stepsToDo) {
if (token.cancelled)
throw new Error('task got cancelled');
// schedule the step to run ASAP, but let other events process first
await new Promise(resolve => setTimeout(resolve, 0));
const chunk = await loadChunk();
updateHash(chunk);
}
(Note that I'm using a Promise constructor here, but awaiting immediately instead of returning it)
The technique above will run slower than just doing the task. But by yielding the thread, stuff like React updates can execute without an awkward hang.
If you really need performance, check out Web Workers, which let you do CPU-heavy work off-thread so it doesn't block the main thread. Libraries like workerize can help you convert async functions to run in a worker.
That's everything I have for now, I'm sorry for writing a novel
I can suggest my library (use-async-effect2) for managing the cancellation of asynchronous tasks/promises.
Here is a simple demo with nested async function cancellation:
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
// just for testing
const factorialAsync = CPromise.promisify(function* (n) {
console.log(`factorialAsync::${n}`);
yield CPromise.delay(500);
return n != 1 ? n * (yield factorialAsync(n - 1)) : 1;
});
function TestComponent({ url, timeout }) {
const [text, setText] = useState("");
const myTask = useAsyncCallback(
function* (n) {
for (let i = 0; i <= 5; i++) {
setText(`Working...${i}`);
yield CPromise.delay(500);
}
setText(`Calculating Factorial of ${n}`);
const factorial = yield factorialAsync(n);
setText(`Done! Factorial=${factorial}`);
},
{ cancelPrevious: true }
);
return (
<div>
<div>{text}</div>
<button onClick={() => myTask(15)}>
Run task
</button>
<button onClick={myTask.cancel}>
Cancel task
</button>
</div>
);
}

Store Promise in Map to resolve/reject later

I'm working on IPC in NodeJS and want to be able to send a message to the parent process from the child and "wait" for the result. My idea was to keep track of all the send messages in a map that maps the unique message ID to a promise. Once the process.on('message`) has been called I lookup the promise by the ID I got back from the parent and want to resolve or reject the promise.
I came up with this, but am stuck at the resolve/reject part:
'use strict'
import RequestMessage from "../messages/request/RequestMessage";
import ResponseMessage from "../messages/response/ResponseMessage";
const process = require('process');
export class CommunicationManager {
private messageQueue: Map<string, Promise<any>>;
constructor() {
this.messageQueue = new Map();
process.on('message', (payload: any) => {
if (payload.hasOwnProperty("_id")
&& this.messageQueue.has(payload.get("_id"))) {
let promise = this.messageQueue.get(payload);
// Resolve or reject the promise..
this.messageQueue.delete(payload.get("_id"));
} else {
console.error(`Got unknown message from parent: ${payload}`);
}
});
}
public execute(message: RequestMessage): Promise<ResponseMessage> {
process.send(message);
this.messageQueue.set(message.id(), // a promise here);
}
}
Can someone push me in the right direction on how to solve this? Is this even possible and best-practice?
Thanks!
You would not store the promise in the map. You would store only the resolver function to call later - the promise is created and returned immediately.
init() {
process.on('message', (payload: any) => {
if ("_id" in payload && this.messageQueue.has(payload._id)) {
const resolve = this.messageQueue.get(payload._id);
this.messageQueue.delete(payload._id);
if (payload.isFulfilled) {
resolve(payload.value);
else {
resolve(Promise.reject(payload.error));
}
} else {
console.error(`Got unknown message from parent: ${payload}`);
}
});
}
public execute(message: RequestMessage): Promise<ResponseMessage> {
return new Promise(resolve => {
this.messageQueue.set(message.id(), resolve);
process.send(message);
});
}
It is rare to call resolve in some other scope than the promise executor's, but messaging is one of those cases where it is necessary and the standard practice. Btw, you might want to consider putting a timeout on the response receival.
#Bergi had a nice answer. I have a follow-up to anyone considering doing something like this: it's a concept called "Deferred/Deferable" that was hot for a while but fell out of favor over async/await - check this guide https://codingbeautydev.com/blog/javascript-resolve-promise-from-outside/ or even this library https://www.npmjs.com/package/deferred
You would then store a deferable in your Map, return deferred.promise in your execute() and call deffered.resolve() whenever you want to actually resolve it.

Checking a variable twice with jest in React [duplicate]

React v15.1.0
Jest v12.1.1
Enzyme v2.3.0
I'm trying to figure out how to test a component that calls a promise in a function invoked by a click. I was expecting Jest's runAllTicks() function to help me out here, but it doesn't seem to be executing the promise.
Component:
import React from 'react';
import Promise from 'bluebird';
function doSomethingWithAPromise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 50);
});
}
export default class AsyncTest extends React.Component {
constructor(props) {
super(props);
this.state = {
promiseText: '',
timeoutText: ''
};
this.setTextWithPromise = this.setTextWithPromise.bind(this);
this.setTextWithTimeout = this.setTextWithTimeout.bind(this);
}
setTextWithPromise() {
return doSomethingWithAPromise()
.then(() => {
this.setState({ promiseText: 'there is text!' });
});
}
setTextWithTimeout() {
setTimeout(() => {
this.setState({ timeoutText: 'there is text!' });
}, 50);
}
render() {
return (
<div>
<div id="promiseText">{this.state.promiseText}</div>
<button id="promiseBtn" onClick={this.setTextWithPromise}>Promise</button>
<div id="timeoutText">{this.state.timeoutText}</div>
<button id="timeoutBtn" onClick={this.setTextWithTimeout}>Timeout</button>
</div>
);
}
}
And the tests:
import AsyncTest from '../async';
import { shallow } from 'enzyme';
import React from 'react';
jest.unmock('../async');
describe('async-test.js', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<AsyncTest />);
});
// FAIL
it('displays the promise text after click of the button', () => {
wrapper.find('#promiseBtn').simulate('click');
jest.runAllTicks();
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
});
// PASS
it('displays the timeout text after click of the button', () => {
wrapper.find('#timeoutBtn').simulate('click');
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('#timeoutText').text()).toEqual('there is text!');
});
});
Updated answer: using async / await leads to cleaner code. Old code below.
I've successfully solved this problem by combining the following elements:
Mock out the promise and make it resolve immediately
Make the test asynchronous by marking the test function async
After simulating the click, wait until the next macrotask to give the promise time to resolve
In your example, that might look like this:
// Mock the promise we're testing
global.doSomethingWithAPromise = () => Promise.resolve();
// Note that our test is an async function
it('displays the promise text after click of the button', async () => {
wrapper.find('#promiseBtn').simulate('click');
await tick();
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
});
// Helper function returns a promise that resolves after all other promise mocks,
// even if they are chained like Promise.resolve().then(...)
// Technically: this is designed to resolve on the next macrotask
function tick() {
return new Promise(resolve => {
setTimeout(resolve, 0);
})
}
Enzyme's update() is neither sufficient nor needed when using this method, because Promises never resolve in the same tick they are created -- by design. For a very detailed explanation of what is going on here, see this question.
Original answer: same logic but slightly less pretty. Use Node's setImmediate to defer the test until the next tick, which is when the promise will resolve. Then call Jest's done to finish the test asynchronously.
global.doSomethingWithAPromise = () => Promise.resolve({});
it('displays the promise text after click of the button', (done) => {
wrapper.find('#promiseBtn').simulate('click');
setImmediate( () => {
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
done();
})
});
This isn't as nice because you'll get big nested callbacks if you have to wait for more than one promise.
There isn't much around needing to somehow wait for the promise to fulfill before ending the test. There are two main ways of doing it from your code that I can see.
independently test that onClick and your promise methods. So check that onClick calls the correct function, but spying on setTextWithPromise, triggering a click and asserting that setTextWithPromise was called. Then you can also get the component instance and call that method which returns the promise you can attach a handler and assert it did the right thing.
expose a callback prop that you can pass in that is called when the promise resolves.

Execute function statement only after promise is resolved

I have the following code which fetches some data by implementing promises.
var TreeDataService = new DataService();
export default class TreeStore {
treeData = [];
getData() {
TreeDataService.get().then(data => {
this.treeData = data;
},
() => {
alert("Error fetching data");
});
return this.treeData; //this returns empty array instead of returning the data fetched from TreeDataService.get
}
}
How can I make return this.treeData execute only after the promise is fully resolved? I know I can put return this.treeData inside then's success method and return the entire getDatamethod as a promise, but that will require again resolving the promise at the call site of getData.
EDIT: I understand that as it's a async operation, I cannot synchronously execute the return statement and can instead return a promise. But then how do I resolve that promise at call site? I am facing the same issue at calling code:
export default class App extends React.Component {
treeData = TreeStoreObj.getData().then(data => {
return data;
}); // This will also execute asynchronously, so will be initially empty.
render() {
return (
<div className="app-container">
<TreeNode node={this.treeData} /> // So this will also be empty
</div>
);
}
}
Earlier code will now be:
export default class TreeStore {
treeData = [];
return getData() {
TreeDataService.get().then(data => {
return this.treeData = data;
},
() => {
alert("Error fetching data");
});
}
}
You are doing something strange. getData should not return any value at all if you are using mobx. It should set store observable only. React component will automatically rerender when this observable value change.
Are you using mobx-react? If so show your integration code. If not try to use it ;)
Async model does not allow you to execute return this.treeData after the promise is resolved. However, you might want to cache the promise not to invoke TreeDataService.get() several times.
For example (untested, just trying to show the main idea):
export default class TreeStore {
treeData = [];
treeDataPromise = null;
getData() {
if (this.treeDataPromise) return this.treeDataPromise;
this.treeDataPromise = TreeDataService.get().then(data => {
this.treeData = data;
return this.treeData;
},
() => {
alert("Error fetching data");
});
return this.treeDataPromise;
}
}
Another option is to check if this.treeData is loaded, and return Promise.resolve(this.treeData) if this is the case.

Promise return undefined AsyncStorage

I have a react-native app where I do some authentication.
I have the following code where I check if the token is not expired and its available.
export const isLogged = () => {
AsyncStorage.getItem('#token')
.then( token => {
if (typeof token !== 'undefined') {
if (tokenExpired(token)) {
return false
}
return true
}
return false
} )
.catch( error => {
return false
} )
}
But in my code if I do:
let isUserLogged = isLogged()
console.log(isUserLogged) // -> returns undefined, but should return true because the token is there and its not expired.
Does anyone has some idea why its like this, I'm doing something wrong?
You are trying to synchronously get a result that only becomes available asynchronously.
Change your code like this:
Add return before this call:
AsyncStorage.getItem('#token')
This will make your isLogged function return something: a promise
Use this promise in your main code:
isLogged().then( isUserLogged => {
console.log(isUserLogged);
});
The fact that your function isLogged returns a promise (when you return it, that is), is an example of chaining.
Your isLogged function is an asynchronous function, that is - it operates on values that might not be available to you in the exact moment of function execution, but delayed in time.
Since you are already operating on Promises here, you could just return the result of your AsyncStorage promise chain, and then attach additional handlers when invoking isLogged() function like this:
// inside your isLogged() function
return AsyncStorage.getItem('#token')
.then(...)
... rest of your code unchanged ...
// when invoking isLogged()
isLogged().then((isLogged) => {
console.log("is user logged: ", isLogged);
});
You should also read more about Promises in JavaScript.

Categories