Clean up after generating function or async generating function - javascript

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();
}
}
}

Related

Protractor JavaScipt function call returns null

I'm trying to call my function from it() but I always get return value of null. I thought my function assigns the value to the return variable back to the caller.
file: "helperDropDownBx.js"
module.exports = function() {
function myFunctionTest() {
var reTxt = null;
try {
browser.wait(EC.visibilityOf(id_dropDownValue), 30000);
id_dropDownValue.getAttribute("value").then(function(text) {
retTxt = text;
});
} catch(err) {
throw new Error(err.message);
}
return retTxt;
}
return{
myFunctionTest : myFunctionTest
}
}
file: "TestHelpers.js"
const myHelper = require("../pages/helpers/helperDropDownBx.js");
describe("[Test Helpers]", function(){
var myHelperObj = new myHelper();
it('testing Helpers', function() {
try{
//attempt#1, but not working
var retVal = myHelperObj.myFunctionTest();
retVal.then(function (value){
console.log(value);
)};
//attempt#2, but not working
myHelperObj.myFunctionTest().then(function(value){
console.log(value);
)};
}catch(err){
throw new Error(err.message);
}
});
});
both of my attempts above, always return null
file: "helperDropDownBx.js"
module.exports = {
myFunctionTest: async function () {
// this is a bad practice to start a function with wait
// all wait should be handled after you complete an action and not before
await browser.wait(EC.visibilityOf(id_dropDownValue), 30000);
return id_dropDownValue.getAttribute("value")
}
}
file: "TestHelpers.js"
const myHelper = require("../pages/helpers/helperDropDownBx.js");
describe("[Test Helpers]", function(){
it('testing Helpers', async function() {
var value = await myHelper.myFunctionTest();
console.log(value)
});
});
if you still curious what you needed to do with .then() to make it work, then something like this
module.exports = {
myFunctionTest: function() {
return browser.wait(EC.visibilityOf(id_dropDownValue), 30000)
.then(() => {
return id_dropDownValue.getAttribute("value")
.then(function(text) {
return text;
})
})
}
}
Looks like hell to me :)

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');

Issue setting callback results equal to object properties (node-tesseract)

I'm trying to use the results of a callback function as properties of an object. Here is how I am trying to use the module I am building:
var Screenshot = require("./Screenshot.js")
const test = async function() {
let screenshot = new Screenshot("./screenshots/Screenshot_20180806093446.jpg")
await screenshot.readScreenshot()
console.log(screenshot.text)
}
test() // logs nothing to console
readScreenshot() doesn't seem to be doing its job. I'm sure there's a finer detail I am missing to making this work.
./Screenshot.js code below:
var tesseract = require("node-tesseract");
module.exports = class Screenshot {
constructor(path) {
this.path = path;
this.readScreenshot = this.readScreenshot.bind(this);
}
readScreenshot() {
tesseract.process(this.path, (err, text) => {
if (err) {
throw err;
} else {
this.text = text.split("\n").filter(el => el.trim() !== "");
}
});
}
};
It would also be nice if I can get the constructor of class Screenshot to properly call this function so I do not have to do it manually in app.js
You will have to change the code so that readScreenshot method allows you to return a promise object.
readScreenshot() {
return new Promise((resolve, reject) => {
tesseract.process(this.path, (err, text) => {
if (err) {
reject(err);
} else {
resolve(text.split("\n").filter(el => el.trim() !== ""))
}
});
});
}
//
var Screenshot = require("./Screenshot.js")
const test = async function() {
let screenshot = new Screenshot("./screenshots/Screenshot_20180806093446.jpg")
let resp = await screenshot.readScreenshot()
console.log(resp)
}
test() // logs nothing to console

How to build an event generator in JavaScript

I am trying to build a way to create a generator which can yield DOM events. More generally, I want to create a way to convert an event system to an async system yielding events.
My initial code example works, but I can see an issue with lifting the resolve function from the Promise so that I can call that function once the event comes in.
class EventPropagation {
constructor(id) {
const button = document.getElementById(id);
let _resolve;
button.addEventListener("click", event => {
if (_resolve) {
_resolve(event);
}
});
let _listen = () => {
return new Promise(resolve => {
_resolve = resolve;
});
}
this.subscribe = async function*() {
const result = await _listen();
yield result;
yield * this.subscribe();
}
}
}
async function example() {
const eventPropagation = new EventPropagation("btn");
for await (const event of eventPropagation.subscribe()) {
console.log(event);
}
}
// call the example function
example();
My question is: Is there a better way of building something like this? There are a lot of things to think about, like multiple events coming in at the same time or cleaning up the listener and the subscriptions. My goal is not to end up with a reactive library but I do want to create small transparent functions which yield events asynchronously.
fiddle
Edited 14 dec 2017 (Edited in response to Bergi's comment)
Async Generators
Babel and a few plugins later; async generators aren't a problem:
const throttle = ms => new Promise(resolve => setTimeout(resolve, ms));
const getData = async() => {
const randomValue = Math.floor(Math.random() * 5000 + 1);
await throttle(randomValue);
return `The random value was: ${randomValue}`;
}
async function* asyncRandomMessage() {
const message = await getData();
yield message;
// recursive call
yield *asyncRandomMessage();
}
async function example() {
for await (const message of asyncRandomMessage()) {
console.log(message);
}
}
// call it at your own risk, it does not stop
// example();
What I want to know is how I transform a series of individual callback calls into an async stream. I can't imagine this problem isn't tackled. When I look at the library Bergi showed in the comments I see the same implementation as I did, namely: "Store the resolve and reject functions somewhere the event handler can call them." I can't imagine that would be a correct way of solving this problem.
You need a event bucket, here is an example:
function evtBucket() {
const stack = [],
iterate = bucket();
var next;
async function * bucket() {
while (true) {
yield new Promise((res) => {
if (stack.length > 0) {
return res(stack.shift());
}
next = res;
});
}
}
iterate.push = (itm) => {
if (next) {
next(itm);
next = false;
return;
}
stack.push(itm);
}
return iterate;
}
;(async function() {
let evts = evtBucket();
setInterval(()=>{
evts.push(Date.now());
evts.push(Date.now() + '++');
}, 1000);
for await (let evt of evts) {
console.log(evt);
}
})();
My best solution thus far has been to have an internal EventTarget that dispatches events when new events are added onto a queue array. This is what I've been working on for a JS modules library (including used modules here). I don't like it... But it works.
Note: This also handles the new AbortSignal option for event listeners in multiple places.
export function isAborted(signal) {
if (signal instanceof AbortController) {
return signal.signal.aborted;
} else if (signal instanceof AbortSignal) {
return signal.aborted;
} else {
return false;
}
}
export async function when(target, event, { signal } = {}) {
await new Promise(resolve => {
target.addEventListener(event, resolve, { once: true, signal });
});
}
export async function *yieldEvents(what, event, { capture, passive, signal } = {}) {
const queue = [];
const target = new EventTarget();
what.addEventListener(event, event => {
queue.push(event);
target.dispatchEvent(new Event('enqueued'));
}, { capture, passive, signal });
while (! isAborted(signal)) {
if (queue.length === 0) {
await when(target, 'enqueued', { signal }).catch(e => {});
}
/**
* May have aborted between beginning of loop and now
*/
if (isAborted(signal)) {
break;
} else {
yield queue.shift();
}
}
}
The example provided by NSD, but now in Typescript
class AsyncQueue<T> {
private queue: T[] = [];
private maxQueueLength = Infinity;
private nextResolve = (value: T) => {};
private hasNext = false;
constructor(maxQueueLength?: number) {
if (maxQueueLength) {
this.maxQueueLength = maxQueueLength;
}
}
async *[Symbol.asyncIterator]() {
while (true) {
yield new Promise((resolve) => {
if (this.queue.length > 0) {
return resolve(this.queue.shift());
}
this.nextResolve = resolve;
this.hasNext = true;
});
}
}
push(item: T) {
if (this.hasNext) {
this.nextResolve(item);
this.hasNext = false;
return;
}
if (this.queue.length > this.maxQueueLength) {
this.queue.shift();
}
this.queue.push(item);
}
}
(async function () {
const queueu = new AsyncQueue<string>();
setInterval(() => {
queueu.push(Date.now().toString());
queueu.push(Date.now().toString() + "++");
}, 1000);
for await (const evt of queueu) {
console.log(evt);
}
})();

"A promise was created in a handler but was not returned from it"

When a user clicks on a button (#lfdsubmit), it calls the function (LFD_SearchContainer()) that should return a promise. But the errors happens at
LFD_SearchContainer('EISU1870725')
.then(container => {
ST2.db2(container);
})
What is wrong?
Code: (don't completely trust the commented out parts to guide you through this code -- i forgot to update some of them)
function LFDTrack () {
function LFD_SearchContainer (requestedContainer) {
return new Promise((resolve, reject) => {
let lfd_scanparams = { TableName: 'lfd_table1' }
db.scan(lfd_scanparams, (err, containers) => {
if (err) {
reject(err);
} else {
containers = containers.Items;
let requestedContainers = []; // different variable than arg
let containerObject; // this will be the resolved object
// this will return the object of the searched container
let findIt = _.forEach(containers, container => {
if (container.container === requestedContainer) {
containerObject = container;
}
});
containerObject = findIt[0];
//console.log(findIt[0]);
resolve(containerObject.container);
}
});
});
}
$(function() {
$("#lfdsubmit").click(function (e) {
e.preventDefault();
let lsd_modaltitle = $("#lfdmodaltitle");
let lsd_modalcontent = $("#lfdmodalcontent");
LFD_SearchContainer('EISU1870725')
.then(container => {
ST2.db2(container); // will send the object
})
.catch(error => {
console.log(error);
});
});
});
}
If ST2.db2(container); returns a promise, you need to change that line to
return ST2.db2(container);
If it doesn't, you can put return null; behind it, like this:
ST2.db2(container);
return null;
Since you didn't provide the definition for ST2, I can't know whether or not the db2 method returns a promise. :)
The error is explained by the author of bluebird here.

Categories