My connections manager is expected to receive connect requests nondeterministically and to handle them in sequence.
How can asynchronous operations be queued such that they are handled later?
I'm not sure of what objects to place in a queue.
Here is my beginner's attempt. Thanks for any help!
class RequestQueue {
private _requests = [];
constructor() {}
public add( rq: any ) {
this._requests.unshift(rq);
}
public remove() {
return this._requests.pop();
}
}
class ConnectionManager {
private requestQueue;
private connecting: boolean = false;
constructor() {
this.requestQueue = new RequestQueue();
}
// ConnectionManager is expected to receive connection requests
// nondeterministically and to handle them in sequence.
public connect(id: string) {
if (!this.connecting) {
this.connecting = true;
return this.asyncConnect(id)
.then(
(result) => result,
(err) => err
)
.then( () => {
this.connecting = false;
if (this.requestQueue.length > 0) {
return this.requestQueue.remove();
}
});
} else {
// how do I queue a someAsyncOp for later?
}
}
private asyncConnect(id: string) : Promise<any> {
return new Promise( (resolve, reject) => {
console.log('begin connect to ', id);
setTimeout(() => {
console.log('end connect to ', id);
resolve();
}, 3000);
});
}
}
function makeConnections() {
const connectionManager = new ConnectionManager();
connectionManager.connect('A');
connectionManager.connect('B');
connectionManager.connect('C');
connectionManager.connect('D');
}
makeConnections();
https://codepen.io/cssbog/pen/BaBMzWW
You have to implement the async serializer pattern
The solution is to keep a queue of Promises that chains them one after the other. It is just a few lines of code and it is a general purpose, allowing any function to be serialized:
const serialize = (fn) => {
let queue = Promise.resolve();
return (...args) => {
const res = queue.then(() => fn(...args));
queue = res.catch(() => {});
return res;
};
};
Related
I am a RxJS beginner, and I want to make my code cleaner.
This is the code I write, I am having problem for receiving completion results.
I want to make my code cleaner, getting result in subscribe callback, without using other Subject to receive result.
Here is my code, rxjs 7.0.1:
import colors from 'colors/safe';
import { Subject } from 'rxjs';
import { throttle } from 'rxjs/operators';
interface IEventTask {
id: string,
createdTime: number
}
let global_counter: number = 0;
const mockHTTPRequest = async (event: IEventTask) => {
return (Promise.resolve().then(async () => {
await new Promise((resolve) => {
global.setTimeout(resolve, 1000);
});
if (event.id === '01') {
throw new Error(`error: ${event.id}`);
}
const result = `result: ${event.id}`;
// I don't know how to get result, so I publish to another Subscriber
subscriber.next(result);
return result;
}))
.catch((error) => {
console.error(error);
})
}
const subject = new Subject<IEventTask>();
subject
.pipe(
throttle(mockHTTPRequest, {
leading: true, trailing: true
})
)
.subscribe({
next: (value) => {
console.log(`${colors.blue(`starting`)} Task#${value.id} at: ${global_counter++}`);
},
error: (error) => {
console.error(error);
},
complete: () => {
// How can I get Promise resolved result here?
console.log(`completed`);
}
});
// It's not the code I want to use, but I don't know how to make it easy.
const subscriber = new Subject<string>();
subscriber.subscribe((result: string) => {
console.log(`${colors.green(`finished`)} ${result} at: ${global_counter++}`);
});
(async () => {
for (let i = 1; i <= 10; i++) {
await new Promise((resolve) => {
global.setTimeout(() => {
resolve(true);
}, 125);
});
const value: IEventTask = {
id: (i).toString().padStart(2, '0'),
createdTime: global_counter++
};
subject.next(value);
}
})();
Thank you very much.
This seems to me like you want to make an HTTP request whenever subject (the variable) gets a new value.
So, you are using throttle with the idea in mind that it should wait for the HTTP request until it is complete and it should provide the returned value. Problem being, that throttle does not provide the latter. (see documentation)
I suggest you use switchMap instead. It expects that the passed function returns an observable and when it emits, then switchMap will forward the value. Furthermore, it completes the inner observable whenever a new value is emitted. Which means that if the HTTP request is not yet completed, it terminates the current request and makes a new one. (see documentation)
import { Subject, Observable, OperatorFunction, pipe, UnaryFunction } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
const mockHTTPRequest = async (event: IEventTask) => {
return (Promise.resolve().then(async () => {
await new Promise((resolve) => {
setTimeout(resolve, 2000);
});
if (event.id === '01') {
throw new Error(`error: ${event.id}`);
}
const result = `result: ${event.id}`;
// This was needed in order to provide the id in the subscribe section
return {
id: event.id,
result
};
}))
.catch((error) => {
console.error(error);
// This was needed for the filter function to work
return undefined;
})
}
// this filters nullish values: null and undefined
function filterNullish<T>(): UnaryFunction<Observable<T | null | undefined>, Observable<T>> {
return pipe(
filter(x => x != null) as OperatorFunction<T | null | undefined, T>
);
}
const subject = new Subject<IEventTask>();
subject
.pipe(
switchMap(mockHTTPRequest),
filterNullish()
)
.subscribe({
next: (value) => {
console.log(`starting Task#${value.id} at: ${global_counter++}`);
},
error: console.error,
complete: () => {
// How can I get Promise resolved result here?
console.log(`completed`);
}
});
In case that you do not want to cancel a request, but rather get the result of every value passed into subject, then you should use look into this comment: https://stackoverflow.com/a/50809667/12851879
Update:
Here is the entire code with mergeMap (so considering all results regardless of the duration)
import colors from 'colors/safe';
import { Subject, Observable, OperatorFunction, pipe, UnaryFunction } from 'rxjs';
import { filter, mergeMap, tap } from 'rxjs/operators';
interface IEventTask {
id: string,
createdTime: number
}
let global_counter: number = 0;
function waitFor(time: number) {
return new Promise((resolve) => {
setTimeout(resolve, time);
})
}
function filterNullish<T>(): UnaryFunction<Observable<T | null | undefined>, Observable<T>> {
return pipe(
filter(x => x != null) as OperatorFunction<T | null | undefined, T>
);
}
const mockHTTPRequest = async (event: IEventTask) => {
return (Promise.resolve().then(async () => {
await waitFor(2000);
if (event.id === '01') {
throw new Error(`error: ${event.id}`);
}
return `result: ${event.id}`;
}))
.catch((error) => {
console.error(error);
return undefined;
})
}
const subject = new Subject<IEventTask>();
subject
.pipe(
tap(value => console.log(`${colors.blue(`starting`)} Task#${value.id} at: ${global_counter++}`)),
mergeMap(mockHTTPRequest),
filterNullish()
)
.subscribe({
next: (result) => {
console.log(`${colors.green(`finished`)} ${result} at: ${global_counter++}`);;
},
error: console.error
});
(async () => {
for (let i = 1; i <= 10; i++) {
await waitFor(125);
const value: IEventTask = {
id: (i).toString().padStart(2, '0'),
createdTime: global_counter++
};
subject.next(value);
}
})();
trying to write unit test using jest it never get into test case where i retruned promise so i can get the response after then.
if there is any better way of writing test cases for promises i would appreciate that input.
executer.ts
export class Executor {
private passedParam: ILogParams = {} as ILogParams;
constructor(public identity: Identity) {
this._ajv = new Ajv();
}
public execute(moduleName: string): (param1, param2) => any {
const self = this;
// getting rid of the tslint issue with Function
return function(params: any, responseCallback: (param: any , param2: any) => any) {
let _mod;
let _httpRequest;
let _params;
Promise.resolve(getApiModule(self.identity, moduleName))
.then((mod: ModuleBase<any, any>) => {
_mod = mod;
mod.ExecStage = ExecStage.Init;
return mod.init(getHttpModule(self.identity), params);
})
.then((httpRequest: HttpRequestBase) => {
_httpRequest = httpRequest;
if (_mod.Response().Summary.Header) {
throw _mod.Response().Summary;
}
return httpRequest;
})
};
}
}
executer.spec.ts
import {ModuleExecutor} from "../../src/ts/common/executor";
it('should transition with the correct event', (done) => {
const executer = new ModuleExecutor(Identity.node);
const executerSpy = executer.execute("Payments/accountBalance/GetAccountBalance");
new Promise((resolve, reject) => {
resolve(true);
}).then(((mod: ModuleBase<any, any>) => {
done();
}))
.catch((error) => {
done();
});
});
I want to implement the classic method chain pattern, the final usage should be
DB
.push(2)
.push(3)
This is the current code, obviously doesn't work, I'm not clear how to return the reference to DB itself resolving the promise
let nodes = [];
let DB = {
self:this,
push: (i) => new Promise((resolve, reject) => {
nodes.push(i)
resolve(this)
})
}
Only a class or function instance has a this reference.
class DB {
constructor() {
this.nodes = [];
this.promise = Promise.resolve();
}
push(i) {
this.nodes.push(i);
return this;
}
pushAsync(i) {
return new Promise((resolve) => {
this.nodes.push(i);
resolve();
});
}
pushAsyncChain(i) {
this.promise.then(() => {
this.promise = new Promise((resolve) => {
this.nodes.push(i);
resolve();
});
});
return this;
}
then(callback) {
this.promise.then(callback);
}
}
const db = new DB();
db.push(2).push(3);
db.pushAsync(4).then(() => db.pushAsync(5));
db
.pushAsyncChain(6)
.pushAsyncChain(7)
.then(() => console.log(db.nodes)); // or await db.promise; console.log(db.nodes);
There are 2 functions that I need to run one-by-one: getUserPlaylists (receives Playlists) and getPlaylistTracks (receives Tracks for provided Playlist).
One response can have up to 50 tracks, so I need to use PageToken if I want to get the rest tracks. The problem is that I can not make a recursive function getPlaylistTracks to wait till the recursion is done.
function getPlaylistsWithTracks () {
return new Promise((resolve, reject) => {
getUserPlaylists()
.then(function (playlists) {
playlists.forEach(
async function (playlistObj) {
await getPlaylistTracks(playlistObj).then(function (tracks) {
playlistObj['tracks'] = tracks
})
})
console.log('resolve')
resolve(playlists)
})
})
}
function getPlaylistTracks (playlistObj, pageToken) {
return new Promise((resolve, reject) => {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
request.execute(function (response) {
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
// Here I need to wait a bit
if (response.result['nextPageToken']) {
getPlaylistTracks(playlistObj, response.result['nextPageToken'])
.then(function (nextPageTracks) {
playlistTracks = playlistTracks.concat(nextPageTracks)
})
}
})
resolve(playlistTracks)
})
}
getPlaylistsWithTracks()
In my case in console I see the next:
> resolve
> pageToken: 123
> pageToken: 345
but, I want to see resolve the last.
How to wait till recursion is executed?
Avoid the Promise constructor antipattern, and (don't) use forEach with async functions properly.
Furthermore, there is nothing special about recursion. It's like any other promise-returning function call that you would want to wait for - put it in your then chain or await it. (The latter is considerably easier).
async function getPlaylistsWithTracks() {
const playlists = await getUserPlaylists();
for (const playlistObj of playlists) {
const tracks = await getPlaylistTracks(playlistObj);
playlistObj.tracks = tracks;
}
console.log('resolve')
return playlists;
}
async function getPlaylistTracks(playlistObj, pageToken) {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
const response = await new Promise((resolve, reject) => {
request.execute(resolve); // are you sure this doesn't error?
});
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
if (response.result['nextPageToken']) {
const nextPageTracks = await getPlaylistTracks(playlistObj, response.result['nextPageToken']);
playlistTracks = playlistTracks.concat(nextPageTracks);
}
return playlistTracks;
}
I don't know if this is could be technically called a race condition...
What I have is a big baby, which can only perform one action at the time, but it's being called from an api endpoint; so simultaneous calls can occur
What I think I need to do is somehow make a queue of actions, return a promise to whoever created it, execute the actions synchronously, and resolve the promise with the value returned by the action
Here is the code (it's no real btw, just a snippet representing the problem):
class Baby {
constructor() {
this.current = 'A'
}
go(from, to) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (from === this.current) {
this.current = to
resolve()
} else {
reject(new Error('Unexpected state'))
}
}, 100)
})
}
// In order to perform the action successfully it has to do some steps in some exact order
async doAction() {
await this.go('A', 'B')
await this.go('B', 'C')
await this.go('C', 'A')
console.log('OK')
}
}
module.exports = new Baby()
And is called like this:
const baby = require('./baby')
for (let i = 0; i < 5; i++) {
doAction()
}
Thanks in advance!
Thanks to Bergi's tips, this is the final solution:
Basically, this keeps a chain of promises and when a new action needs to be added it's chained to the current chain of promises
const baby = {
current: 'A',
action: Promise.resolve(),
go(from, to) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (from === this.current) {
this.current = to
resolve()
} else {
reject(new Error('Unexpected state'))
}
}, 100)
})
},
doAction() {
this.action = this.action.then(async () => {
await this.go('A', 'B')
await this.go('B', 'C')
await this.go('C', 'A')
console.log('OK')
})
}
}
baby.doAction()
baby.doAction()
baby.doAction()