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.
Related
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>
);
}
There is some Advanced topics in JS I am learning and there is something I am trying to understand.
I've written a VueJS application and I need to expose some data and possibly methods from Vue outside of Vue itself. I am using vuex.
This is so that users can use normal JS on the front end to extend the application.
The idea is the user would add something like this on the front end. I have seen several JS frameworks/apps that do something like this.
AppName.onReady.then(function(instance) {
// use the instance object
var email = instance.data["email"]
var name = instance.data["name"]
var isComplete = member.isComplete
})
Now what I am trying to understand is the code I would need in my App to make the above work.
I know from the code it is a Class AppName that is invoked with new AppName({}) and it has a promise that gets resolved but not sure how to accomplish it.
Here is a sample for returning a promise.
const AppName = {
onReady() {
return new Promise( (resolve, reject) => {
// do your stuff here, then call resolve().
// sample follows
setTimeout(()=>{resolve()}, 1000)
})
}
}
Call it like this. Make sure to invoke onReady(), not just onReady.
AppName.onReady().then(()=>{
// your code here.
})
Here is one way you can can achieve that effect:
class AppName {
constructor(data = {
email: null,
name: null
}) {
this.data = data;
}
get onReady() {
return new Promise((resolve, reject) => {
resolve(this);
});
}
}
const handleOnReady = instance => {
const {
email,
name
} = instance.data;
console.log(`${name}'s email address is: ${email}.`);
};
const tom = {
email: 'tom#fakemail.com',
name: 'Tom'
};
const app = new AppName(tom);
app.onReady.then(handleOnReady);
You can mark a method as async. Then it returns a promise implicitly.
const AppName = {
async onReady() {
// do your stuff here, then simply return to resolve.
// throwing a exception calls reject.
}
}
Usage:
AppName.onReady().then(()=>{
// app is ready
})
If your client is in an async function, you can simply use await
Usage:
// must be inside async function
await AppName.onReady()
// app is ready
To use await at root level, wrap it in immediate executed function:
(async()=>{
await AppName.onReady()
})()
I started switching my project from standard Fetch API to Axios library. Axios seemed great, with all the interceptors, custom instances, etc. The problem started with POST requests.
I have an custom axios instance defined as:
export const axiosInstance = axios.create({
baseURL: API_URL,
timeout: 10000
});
Im using it for most of my API calls and most of them are fine, except one that I got stuck on for a incredibly frustrating amount of time.
Using POST request, it seems that the Promise is resolved into undefined.
Lets take a look at a code:
export async function saveIncomes(incomes) {
const { added, changed } = incomes;
const add_res = axiosInstance.post(`${INCOMES_URL}`, added).then(console.log);
const change_responses = [];
for (let changed_row of changed) {
change_responses.push(
axiosInstance.put(`${INCOMES_URL}${changed_row.id}/`, changed_row)
);
}
let finalRes = [];
try {
finalRes = await axios.all([add_res, ...change_responses]);
} catch (e) {
console.log(e);
}
return finalRes;
}
This function takes two arrays of incomes - added and changed (since two http methods),
prepare all promises for them (I have bulk POST but not PUT on my API) and call axios.all to run them concurrently. Now, the fun begins.
When I post the correct data that is validated by API and returns 201 created, all is fine, the Promise resolves to current axios Response object, but when the data is incorrect and status is 400, it resolves to undefined.
Example:
axios.all([p1, p2, p3]) // correct data
-> [<response1>, <response2>, <response3>
axios.all([p1, p2, p3]) // incorrect data
-> [undefined, undefined, undefined]
It doesnt throw an error, it resolves, but to no avail.
The browser however gets the data correctly (I mean, its status 400, but there IS an response body).
I have no clue what to do anymore, I'm new to axios but it looked less problematic then it is now.
My frontend app is on React.js, some parts of it still uses fetch API, because its a work in progress.
Backend is in python Django with DRF.
EDIT:
Also, I'm using interceptors heres the code:
export function setResponseInterceptor({ onSuccess, onError }) {
if (!onSuccess) {
onSuccess = response => response;
}
axiosInstance.interceptors.response.use(response => {
if (isHandlerEnabled(response.config)) {
response = onSuccess(response);
}
console.log(response);
return response;
}, onError);
}
export function setRequestInterceptor({ beforeSend, onError }) {
if (!beforeSend) {
beforeSend = cfg => cfg;
}
axiosInstance.interceptors.request.use(cfg => {
if (isHandlerEnabled(cfg)) {
cfg = beforeSend(cfg);
}
return cfg;
}, onError);
}
Axios call Promise.all() on axios.all(), which run promises asynchroniously. Looking at MDN definition of promises .all reject you can see the following :
Using Promise.all
Promise.all waits for all fulfillments (or the first rejection).
Rejection
If any of the passed-in promises reject, Promise.all asynchronously rejects with the value of the promise that rejected, whether or not the other promises have resolved.
As your API return 401, it return the rejection of the failling promise, ignoring the others.
Catch a rejection
Using .catch, you will receive a unique promise rejection as argument and should be able to read it's value.
// Using .catch:
Promise.all([p1, p2, p3, p4, p5])
.then(values => {
console.log(values);
})
.catch(error => {
console.error(error.message)
});
Looking at your code, you need to make sure your function saveIncomes handle properly this behaviour.
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.
I am trying to understand how developers use Promise with React-Native. It would be great to get feedback and recommendations on how to setup API calls and handle the data. Please understand I never used Promise before and that I am new to React-Native.
Thank you in advance. Any resource about this subject is welcome too.
Pseudocode
Child
Retrieve two variables
Use these two variables to build an URL
Trigger the first Promise and resolve
Retrieve another two variables
Use these two variables to build a new an URL
Trigger the second Promise and resolve
Gather the data from both promises and pass to parent
Parent
Retrieve data from Child
Get data from the first Promise and set to a state
Get data from the second Promise and set to another state
APIservice.js
Child
Is it a good practice to setup all your API calls in a separate file? It's likely that in the future I will need to make different API calls, would you create multiple functions to handle that?
class APIservice {
_getStopPoint = (endpoint) => {
return new Promise(function(resolve, reject) {
fetch(endpoint)
.then((response) => response.json())
.then((data) => {
console.log("APIservice StopPoint", data)
resolve(data);
});
});
};
};
module.exports = new APIservice
App.js
Parent
As you can see, the way I setup the endpoint is lame. It's not ideal as the URL is the same. I want to structure something that can receive two variables and build the URL on the go. Something like https://api.tfl.gov.uk/Line/${routeid}/Arrivals/${stationid}.
If I manage that, how can I pass the API call to the APIservice having only one endpoint that dynamically will change based on the two variables it receives? I am not sure how to differentiate the call in the Promise.all having only "one" URL.
That brings me another issue. When setting the state in App.js, should I setState using the specifics array from data? Something like bus: data[0], tube: data[1]. Is this a good practice?
let APIservice = require('./APIservice')
let endpoint = 'https://api.tfl.gov.uk/Line/55/Arrivals/490004936E'
let endpoint1 = 'https://api.tfl.gov.uk/Line/Northern/Arrivals/940GZZLUODS'
let loadData = (endPoint) => {
// Multiple API calls
Promise.all([
APIservice._getStopPoint(endpoint),
APIservice._getStopPoint(endpoint1),
])
.then((data) => {
console.log("App.js", data)
})
.catch((error) => {
console.log(error)
})
}
export default class App extends Component {
componentWillMount() {
// URL fetch based on variables, not dynamic
loadData(endpoint)
loadData(endpoint1)
}
render() {
loadData("hello")
return (
<View style={styles.container}>
<Text>
Promise
</Text>
</View>
);
}
}
you can try this example
const callbackFn = (firstName, callback) => {
setTimeout(() => {
if (!firstName) return callback(new Error('no first name
passed in!'))
const fullName = `${firstName} Doe`
return callback(fullName)
}, 2000)
}
callbackFn('John', console.log)
callbackFn(null, console.log)