One Promise from multiple files - javascript

The example below shows a snippet of my application. The assumption is: some functions are executed after the document is loaded, others only after the POST response. On this momet, callbacks are placed in the array and executed after the request is completed. It all works as expected. I would like to change these callbacks to one Promise which would be fired just like now "Update.endRequest()"; I wonder if it is possible? Any suggestions are welcome. Or an idea if it can be done better.
update.js
class UpdateClass {
constructor() {
this.callbacks = [];
}
success(callback) {
this.callbacks.push(callback);
}
endRequest() {
this.callbacks.forEach(callback => callback());
}
}
const Update = new UpdateClass();
export default Update;
file1.js
import Update from './update';
export default class File1Class {
constructor () {
this.init();
}
init() {
Update.success(this.update);
exampleFunction();
}
update() {
exampleFunctionAfterUpdate();
}
}
function exampleFunction() {
console.log('On document load 1');
}
function exampleFunctionAfterUpdate() {
console.log('After Update 1');
}
file2.js
import Update from './update';
export default class File2Class {
constructor () {
this.init();
}
init() {
Update.success(this.update);
anotherExampleFunction();
}
update() {
anotherExampleFunctionAfterUpdate();
}
}
function anotherExampleFunction() {
console.log('On document load 2');
}
function anotherExampleFunctionAfterUpdate() {
console.log('After Update 2');
}
app.js
import Update from './update';
import File1Class from './file1';
import File2Class from './file2';
class AppClass {
constructor () {
this.init();
}
init() {
new File1Class();
new File2Class();
triggerfunction();
}
}
function triggerfunction() {
const button = document.getElementById('trigger');
button.addEventListener('click', () => {
xhr.open('POST', 'someUrl', true);
xhr.onload = () => {
if (xhr.status === 200) {
Update.endRequest();
}
};
xhr.send(someJsonData);
});
}
window.app = new AppClass();

You can use something like this
const promiseObj = new Promise((resolve,reject)=>{
exampleFunctionAfterUpdate();
anotherExampleFunctionAfterUpdate();
resolve(true);
});
function triggerfunction() {
const button = document.getElementById('trigger');
button.addEventListener('click', () => {
xhr.open('POST', 'someUrl', true);
xhr.onload = () => {
if (xhr.status === 200) {
// Update.endRequest();
promiseObj.then(()=> console.log('Functions executed'));
}
};
xhr.send(someJsonData);
});
}

Related

I need to send event to the instance after fetching. Javascript Class

I want to call a function from a method. I don't even know how to ask correctly. test.onload() is called immediately, not after 3 seconds. See code for example, please.
export default class {
constructor() {
// some code
setTimeout(() => {
this.onload;
}, 3000);
}
onload = (fn) => {
console.log('loaded event');
fn();
};
}
const test = new TEST();
test.onload(function () {
console.log('from instance');
});
You are calling the function directly. Somehow you think that will be called when the setTimeout runs which is not the case.
If you want the function "from instance" to be called you need to rethink how you are registering it. You are going to have to store the function somehow and let the timer pick it up and execute it.
Setting it with the constructor
class TEST {
constructor(callback) {
this.callback = callback;
setTimeout(() => this.onload(), 3000);
}
onload = () => {
console.log('loaded event');
if(this.callback) this.callback();
};
}
const test = new TEST(function () {
console.log('from instance');
});
Setting it with a method
class TEST {
constructor() {
setTimeout(() => this.onload(), 3000);
}
onload = () => {
console.log('loaded event');
if(this.callback) this.callback();
};
registerCallback(callback) {
this.callback = callback;
}
}
const test = new TEST();
test.registerCallback(function () {
console.log('from instance');
});

How would I prevent an event until the code is done via import/export?

=================== Progress Update ===================
I've made my first Async function according to Mozilla and another tutorial. However, it couldn't prevent the event as I expected. It goes stacking when I click multiple times before the whole code is done.
My expectation is using async and promise to disable the event until the entire code is done which is the way that I use callback like this CodePen example.
Additionally, I can't fully understand the concept of Async and Promise. I think the Async + Promise function separates the code as itself such as the bookmark of the book in real life? It's really hard to understand what's going on inside of the code.
Can somebody explain to me that how async and promise work in the code and prevent the event?
This is a result that I've done so far:
class Async {
constructor(elem) {
this.elem = document.querySelectorAll(elem)
this.flag = true;
this.selector(this.elem, 'click');
}
selector(node, eventName) {
node.forEach(item => {
item.addEventListener(eventName, (e) => this.group(e))
})
}
waiting() {
if (this.flag) {
this.flag = false;
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('waiting . . .'))
}, 2000)
})
}
}
result() {
console.log('test');
this.flag = true;
}
async group(e) {
const a = await this.waiting();
const b = await this.result();
}
}
const async = new Async('.button');
=================== Original Question ===================
I'm separating the code blocks with import/export in Node js for looking clearer when I refactoring it.
A problem is boolean flag called this.flag doesn't prevent the event overriding when I pass it to init.js as a parameter like this:
See the code first:
// =================== init ===================
'use strict';
import Terminal from './terminal.js';
import Touch from './touch.js';
export const slider = (function() {
class Slider {
constructor(elem) {
this.elem = document.querySelector(elem);
this.flag = false;
this.terminal = new Terminal(this.elem);
this.touch = new Touch(this.elem);
this.terminal.insert(this.elem, 'click', this.touch.clicked.bind(this.touch));
}
wait(flag, callback) {
flag = false; // the boolean can't prevent the event overriding right now.
let bound = callback.bind(this);
console.log('waiting . . .');
setTimeout(bound, 1000, flag);
}
done(flag) {
console.log('done');
flag = true;
}
}
return {
init: new Slider('.contents')
}
}())
// =================== terminal.js ===================
export default class Terminal {
constructor(elem) {
this.elem = elem;
}
insert(node, eventName, callback) {
node.addEventListener(eventName, callback);
}
}
// =================== touch.js ===================
import {slider} from './a.js';
export default class Touch {
constructor(elem) {
this.elem = elem;
this.flag = true;
}
clicked(e) {
if (this.flag) {
console.log(`clicked`);
let slide = slider;
slide.init.wait(this.flag, slide.init.done);
}
}
}
But the weird thing is, when I replace the both functions wait() and result() to touch.js, it prevents the event until the countdown is done.
// touch.js
wait(callback) {
this.flag = false;
let bound = callback.bind(this);
console.log('waiting . . .');
setTimeout(bound, 1000);
}
done(flag) {
console.log('done');
this.flag = true;
}
I would like to know why the flag can't prevent the event when it passed to the another js file and how to make it disables the event temporarily.
Explanation
Generally speaking
The async/await pattern got introduced with ES2017 of JavaScript and is syntactic sugar for the older Promise's
Some older browsers don't support async/await
async is a newer way of saying return Promise((resolve) => { ... }).
await is the counterpart to async and is the newer way of saying .then(result => { ... }).
await can only be used in functions marked as async
try/catch is the counterpart to .catch(error => { ... }). It is not actually new, but you can use it in this context.
You can read more about async/await here
Code
I have made some minor changes to your code, so that it makes more sense and written some comments, so that you understand everything that is happening here.
class Async {
constructor(elem) {
this.elem = document.querySelectorAll(elem)
this.isRunning = false; // <-- Rename the flag variable to something more meaningful
this.selector(this.elem, 'click');
}
selector(node, eventName) {
node.forEach(item => {
item.addEventListener(eventName, (e) => this.group(e))
})
}
waiting() {
return new Promise((resolve, reject) => { // <-- Move the Promise to here so that every codepath returns something
if (!this.isRunning) {
this.isRunning = true;
console.log('Waiting ... '); // <-- Move the waiting before the timeout, because inside it is not actually waiting, its rather done
setTimeout(() => { // <-- setTimeout runs the provided function after the provided time in milliseconds elapsed
this.isRunning = false; // <-- Switch the isRunning after the timeout, because that makes more sense (because now it is not running anymore)
resolve('Done'); // <-- Change the text to done and actually resolve it (eg. remove the console.log)
}, 2000)
} else {
reject('There is already a button function running'); // <-- reject is like throwing an error
}
})
}
result() {
console.log('test');
}
async group(e) {
try {
const a = await this.waiting(); // <-- Assigns 'Done' to the variable a
console.log(a); // <-- prints the message
this.result(); // <-- prints 'test' immidiatly after the above console.log
} catch (error) {
console.log(error); // <-- Prints the reject message in case the button is already running
}
/* group could also be written with the old syntax like this:
this.waiting().then(result => {
console.log(result); // Will print "Done" after 2000 milliseconds
this.result(); // Will print test instantly after the above console.log(). You dont need to await it, because it is not an async function
}).catch(error => {
console.log(error); // Will print the reject message in case the button is already running
});
*/
}
}
const asyncButton = new Async('.button'); // <-- Don't use async as a variable name. It's a reserved keyword
Running Example
It is the same code again, but without comments, so that you can test it directly here on StackOverflow.
class Async {
constructor(elem) {
this.elem = document.querySelectorAll(elem)
this.isRunning = false;
this.selector(this.elem, 'click');
}
selector(node, eventName) {
node.forEach(item => {
item.addEventListener(eventName, (e) => this.group(e))
})
}
waiting() {
return new Promise((resolve, reject) => {
if (!this.isRunning) {
this.isRunning = true;
console.log('Waiting ... ');
setTimeout(() => {
this.isRunning = false;
resolve('Done');
}, 2000)
} else {
reject('There is already a button function running');
}
})
}
result() {
console.log('test');
}
async group(e) {
try {
const a = await this.waiting();
console.log(a);
this.result();
} catch(error) {
console.log(error);
}
}
}
const asyncButton = new Async('.button');
<button class="button">Test</button>
Got a solution. Adding await functions into the boolean flag is working.
class Async {
constructor(elem) {
this.elem = document.querySelectorAll(elem)
this.flag = true;
this.selector(this.elem, 'click');
}
selector(node, eventName) {
node.forEach(item => {
item.addEventListener(eventName, (e) => this.group(e))
})
}
waiting() {
return new Promise(resolve => {
setTimeout(() => {
resolve(console.log('waiting . . .'))
}, 1000)
})
}
result() {
console.log('test');
this.flag = true;
}
async group(e) {
// console.log(this.flag);
if (this.flag) {
this.flag = false;
console.log('test');
const a = await this.waiting();
const b = await this.result();
}
}
}
const async = new Async('.button');

Pass a parameter into a function setting a setTimeout?

Is there a way I can call this from another module specifying the timeout in brackets?
var addActiveDiv = () => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, 1850);
}
addActiveDiv();
export { addActiveDiv }
Something like
addActiveDiv(2000); where 2000 is the new timeout?
Just specify the function as taking a parameter, and pass that parameter to setTimeout?
var addActiveDiv = (ms) => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, ms);
}
addActiveDiv(2000);
export { addActiveDiv }
Use parameters or default parameters.
var addActiveDiv = (timeout = 1850) => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, timeout);
}
addActiveDiv();
export { addActiveDiv }
import { addActiveDiv } from "foobar";
addActiveDiv(2000);

Inconsitant mergeMap behaviour

I am currently working on a file uploading method which requires me to limit the number of concurrent requests coming through.
I've begun by writing a prototype to how it should be handled
const items = Array.from({ length: 50 }).map((_, n) => n);
from(items)
.pipe(
mergeMap(n => {
return of(n).pipe(delay(2000));
}, 5)
)
.subscribe(n => {
console.log(n);
});
And it did work, however as soon as I swapped out the of with the actual call. It only processes one chunk, so let's say 5 out of 20 files
from(files)
.pipe(mergeMap(handleFile, 5))
.subscribe(console.log);
The handleFile function returns a call to my custom ajax implementation
import { Observable, Subscriber } from 'rxjs';
import axios from 'axios';
const { CancelToken } = axios;
class AjaxSubscriber extends Subscriber {
constructor(destination, settings) {
super(destination);
this.send(settings);
}
send(settings) {
const cancelToken = new CancelToken(cancel => {
// An executor function receives a cancel function as a parameter
this.cancel = cancel;
});
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.next([null, resp.data]))
.catch(e => this.next([e, null]));
}
next(config) {
this.done = true;
const { destination } = this;
destination.next(config);
}
unsubscribe() {
if (this.cancel) {
this.cancel();
}
super.unsubscribe();
}
}
export class AjaxObservable extends Observable {
static create(settings) {
return new AjaxObservable(settings);
}
constructor(settings) {
super();
this.settings = settings;
}
_subscribe(subscriber) {
return new AjaxSubscriber(subscriber, this.settings);
}
}
So it looks something like this like
function handleFile() {
return AjaxObservable.create({
url: "https://jsonplaceholder.typicode.com/todos/1"
});
}
CodeSandbox
If I remove the concurrency parameter from the merge map function everything works fine, but it uploads all files all at once. Is there any way to fix this?
Turns out the problem was me not calling complete() method inside AjaxSubscriber, so I modified the code to:
pass(response) {
this.next(response);
this.complete();
}
And from axios call:
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.pass([null, resp.data]))
.catch(e => this.pass([e, null]));

Clean up after generating function or async generating function

How do I clean up after a generating function has been exited. The problem I am having is I made a little utility to read-lines from a file using Async Iterators a stage 3 EcmaScript proposal and I want it to close the file it's reading after I exit a for-of loop. Currently since this feature is only stage 3, in order to get this to run you'll have to use babel to transpile it.
With the below code you can see the problem. If you pipe in an input file then it will read one line and print that the line reader is open still.
I would like to explicitly close the file inside the LineReader class when it's iterator is returned.
I know I can do this by not using a generating function but instead return an iterator object as outlined here, but is there any way I can keep the generating function and define a return method for it.
src/line-reader.js
function deferred() {
const def = {}
def.promise = new Promise((resolve, reject) => {
def.resolve = resolve
def.reject = reject
})
return def
}
/**
* PromiseQueue from GTOR adapted for ES2015
* https://github.com/kriskowal/gtor
*/
class PromiseQueue {
constructor (values) {
this.ends = deferred();
if (values) {
values.forEach(this.put, this);
}
}
put(value) {
const next = deferred();
this.ends.resolve({
head: value,
tail: next.promise
});
this.ends.resolve = next.resolve;
}
get () {
var result = this.ends.promise.then(node=>node.head);
this.ends.promise = this.ends.promise.then(node=>node.tail)
return result;
};
}
class LineReader {
constructor (input, output) {
this.lineReader = require('readline').createInterface({ input, output });
this.lineQueue = new PromiseQueue();
this.isClosed = false;
this.lineReader.on('line', (line) => {
this.lineQueue.put(line);
this.lineReader.pause();
});
this.lineReader.on('close', (line) => {
this.isClosed = true;
this.lineQueue.put(Promise.resolve({done: true}));
});
this.lineReader.on('SIGCONT', () => {
// `prompt` will automatically resume the stream
this.lineReader.prompt();
});
}
readLine(){
var result = this.lineQueue.get().then(function (data) {
if(data && data.done) {
throw new Error("EOF");
}
return data
});
this.lineReader.resume();
return result;
}
close () {
this.lineReader.close();
}
async * [Symbol.asyncIterator] () {
try {
while(!this.isClosed) {
yield await this.readLine();
}
} catch (e) {
this.close();
}
}
}
module.exports = LineReader;
test/test.js
var LineReader = require("../src/line-reader");
var lineReader = new LineReader(process.stdin);
(async ()=> {
var i = 1;
for await (var line of lineReader) {
console.log(`${i++} ${line}`);
break;
}
console.log(`The line-reader is ${lineReader.isClosed ? "closed" : "open" }.`);
lineReader.close();
})().catch(e=> {
console.error(e)
})
You can always Object.assign over a called generating function and extend its returned iterator with your return method.
Like so:
class LineReader {
// ... elided code ...
async * iterate() {
try {
while(!this.isClosed) {
yield await this.readLine();
}
} catch (e) {
this.close();
}
}
[Symbol.asyncIterator] () {
return Object.assign(this.iterate(), {
return: () => {
this.close();
}
});
}
}
Or optionally you can do it this way, but I prefer the first as it doesn't create a new function each time it's called and the first looks much better to me.
class LineReader {
// ... elided code ...
[Symbol.asyncIterator] () {
var that = this;
return Object.assign(async function * iterate() {
try {
while(!that.isClosed) {
yield await that.readLine();
}
} catch (e) {
that.close();
}
}(), {
return: () => {
this.close();
}
});
}
}
Just add a finally to your try block. finally will execute even if the function has returned (which it returns when someone breaks out of a for of loop). This guarantees that you function will clean up and you don't have to modify your function much. I just found this out thanks to this article by Jake Archibald.
class LineReader {
// ... elided code ...
async * [Symbol.asyncIterator] () {
try {
while(!this.isClosed) {
yield await this.readLine();
}
} finally {
this.close();
}
}
}

Categories