Is there a way to set backpressure on a dart stream?
I want to implement a function like this:
The user presses a button.
Add data to PublishSubject.
Even if other data is added while the added data is being processed, it is ignored.
I don't know how to this it.
Any good way?
Dart Stream has built-in backpressure (buffered).
Stream.periodic(const Duration(milliseconds: 100), (i) => i)
.take(5)
.listen((v) async {
await Future<void>.delayed(const Duration(milliseconds: 500));
print(v);
});
await Future<void>.delayed(const Duration(seconds: 10));
// will print 0, 1, 2, 3, 4
You can use exhaustMap of rxdart to ignore/drop values.
Stream.periodic(const Duration(milliseconds: 100), (i) => i)
.take(5)
.exhaustMap((v) => Rx.timer(v, const Duration(milliseconds: 500)))
.listen(print);
await Future<void>.delayed(const Duration(seconds: 10));
// will print 0
Related
For a digital artwork I'm generating a canvas element in Vue which draws from an array of multiple images.
The images can be split in two categories:
SVG (comes with a fill-color)
PNG (just needs to be drawn as a regular image)
I came up with this:
const depict = (options) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const myOptions = Object.assign({}, options);
if (myOptions.ext == "svg") {
return loadImage(myOptions.uri).then((img) => {
ctx.drawImage(img, 0, 0, 100, 100);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = myOptions.clr;
ctx.fillRect(0, 0, 100, 100);
ctx.globalCompositeOperation = "source-over";
});
} else {
return loadImage(myOptions.uri).then((img) => {
ctx.fillStyle = myOptions.clr;
ctx.drawImage(img, 0, 0, 100, 100);
});
}
};
this.inputs.forEach(depict);
for context:
myOptions.clr = the color
myOptions.uri = the url of the image
myOptions.ext = the extension of the image
While all images are drawn correctly I can't figure out why the last fillStyle overlays the whole image. I just want all the svg's to have the fillStyle which is attached to them.
I tried multiple globalCompositeOperation in different orders. I also tried drawing the svg between ctx.save and ctx.restore. No succes… I might be missing some logic here.
So! I figured it out myself in the meantime :)
I created an async loop with a promise. Inside this I created a temporary canvas per image which I then drew to one canvas. I took inspiration from this solution: https://stackoverflow.com/a/6687218/15289586
Here is the final code:
// create the parent canvas
let parentCanv = document.createElement("canvas");
const getContext = () => parentCanv.getContext("2d");
const parentCtx = getContext();
parentCanv.classList.add("grid");
// det the wrapper from the DOM
let wrapper = document.getElementById("wrapper");
// this function loops through the array
async function drawShapes(files) {
for (const file of files) {
await depict(file);
}
// if looped > append parent canvas to to wrapper
wrapper.appendChild(parentCanv);
}
// async image loading worked best
const loadImage = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`load ${url} fail`));
img.src = url;
});
};
// depict the file
const depict = (options) => {
// make a promise
return new Promise((accept, reject) => {
const myOptions = Object.assign({}, options);
var childCanv = document.createElement("canvas");
const getContext = () => childCanv.getContext("2d");
const childCtx = getContext();
if (myOptions.ext == "svg") {
loadImage(myOptions.uri).then((img) => {
childCtx.drawImage(img, 0, 0, 100, parentCanv.height);
childCtx.globalCompositeOperation = "source-in";
childCtx.fillStyle = myOptions.clr;
childCtx.fillRect(0, 0, parentCanv.width, parentCanv.height);
parentCtx.drawImage(childCanv, 0, 0);
accept();
});
} else {
loadImage(myOptions.uri).then((img) => {
// ctx.fillStyle = myOptions.clr;
childCtx.drawImage(img, 0, 0, 100, parentCanv.height);
parentCtx.drawImage(childCanv, 0, 0);
accept();
});
}
});
};
drawShapes(this.inputs);
I use the code proposed as an example in the documentation for Domino AppDev Pack 1.0.4 , the only difference is the reading of a text file (body.txt) as a buffer, this file containing only simple long text (40Ko).
When it is executed, the document is created in the database and the rest of the code does not return an error.
But finally, the rich text field was not added to the document.
Here the response returned:
response: {"fields":[{"fieldName":"Body","unid":"8EA69129BEECA6DEC1258554002F5DCD","error":{"name":"ProtonError","code":65577,"id":"RICH_TEXT_STREAM_CORRUPT"}}]}
My goal is to write very long text (more than 64 Ko) in a rich text field. I use in the example a text file for the buffer but it could be later something like const buffer = Buffer.from ('very long text ...')
Is this the right way or does it have to be done differently ?
I'm using a Windows system with IBM Domino (r) Server (64 Bit), Release 10.0.1FP4 and AppDevPack 1.0.4.
Thank you in advance for your help
Here's code :
const write = async (database) => {
let writable;
let result;
try {
// Create a document with subject write-example-1 to hold rich text
const unid = await database.createDocument({
document: {
Form: 'RichDiscussion',
Title: 'write-example-1',
},
});
writable = await database.bulkCreateRichTextStream({});
result = await new Promise((resolve, reject) => {
// Set up event handlers.
// Reject the Promise if there is a connection-level error.
writable.on('error', (e) => {
reject(e);
});
// Return the response from writing when resolving the Promise.
writable.on('response', (response) => {
console.log("response: " + JSON.stringify(response));
resolve(response);
});
// Indicates which document and item name to use.
writable.field({ unid, fieldName: 'Body' });
let offset = 0;
// Assume for purposes of this example that we buffer the entire file.
const buffer = fs.readFileSync('/driver/body.txt');
// When writing large amounts of data, it is necessary to
// wait for the client-side to complete the previous write
// before writing more data.
const writeData = () => {
let draining = true;
while (offset < buffer.length && draining) {
const remainingBytes = buffer.length - offset;
let chunkSize = 16 * 1024;
if (remainingBytes < chunkSize) {
chunkSize = remainingBytes;
}
draining = writable.write(buffer.slice(offset, offset + chunkSize));
offset += chunkSize;
}
if (offset < buffer.length) {
// Buffer is not draining. Whenever the drain event is emitted
// call this function again to write more data.
writable.once('drain', writeData);
}
};
writeData();
writable = undefined;
});
} catch (e) {
console.log(`Unexpected exception ${e.message}`);
} finally {
if (writable) {
writable.end();
}
}
return result;
};
As of appdev pack 1.0.4, the rich text stream accepts writing data of valid rich text cd format, in the LMBCS character set. We are currently working on a library to help you write valid rich text data to the stream.
I'd love to hear more about your use cases, and we're excited you're already poking around the feature! If you can join the openntf slack channel, I usually hang out there.
I need to know how puppeteer handles the click object, as well as Chromium DevTools API. I've tried to research it on my own and have found myself not being able to find the actual code that handles it.
The reason why I need to know is I'm developing a wrapper that tests events in code for testing Web Pages, and was looking to see if implementing a event handling routine is beneficial instead of using puppeteers interface of events (clicks and taps an hover, as well as other events that might be needed like touch events, or scrolling)
Here is how far I've gotten:
Puppeteer API uses the Frame Logic of DevTools to contact API:
https://github.com/puppeteer/puppeteer/blob/master/lib/Page.js
/**
* #param {string} selector
* #param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
*/
click(selector, options = {}) {
return this.mainFrame().click(selector, options);
}
/**
* #return {!Puppeteer.Frame}
*/
/**
* #param {!Protocol.Page.Frame} framePayload`
*/
_onFrameNavigated(framePayload) {
const isMainFrame = !framePayload.parentId;
let frame = isMainFrame ? this._mainFrame : this._frames.get(framePayload.id);
assert(isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame');
// Detach all child frames first.
if (frame) {
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
}
if (isMainFrame) {
if (frame) {
// Update frame id to retain frame identity on cross-process navigation.
this._frames.delete(frame._id);
frame._id = framePayload.id;
} else {
// Initial main frame navigation.
frame = new Frame(this, this._client, null, framePayload.id);
}
this._frames.set(framePayload.id, frame);
this._mainFrame = frame;
}
This is as far as I have gotten because I've tried to look up the Page Protocol but I can't figure out what happens there.
Any help would be appreciated, even in research.
The main parts are happening in JSHandle here:
async click(options) {
await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint();
await this._page.mouse.click(x, y, options);
}
It scrolls until the element is in viewport (otherwise it won't click) which is happening here, then it finds the clickable coordinates on the element using DevTools API here:
async _clickablePoint() {
const [result, layoutMetrics] = await Promise.all([
this._client.send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId
}).catch(debugError),
this._client.send('Page.getLayoutMetrics'),
]);
if (!result || !result.quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Filter out quads that have too small area to click into.
const {clientWidth, clientHeight} = layoutMetrics.layoutViewport;
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1);
if (!quads.length)
throw new Error('Node is either not visible or not an HTMLElement');
// Return the middle point of the first quad.
const quad = quads[0];
let x = 0;
let y = 0;
for (const point of quad) {
x += point.x;
y += point.y;
}
return {
x: x / 4,
y: y / 4
};
}
and finally it moves the mouse to the coordinate here and clicks on it here
async click(x, y, options = {}) {
const {delay = null} = options;
if (delay !== null) {
await Promise.all([
this.move(x, y),
this.down(options),
]);
await new Promise(f => setTimeout(f, delay));
await this.up(options);
} else {
await Promise.all([
this.move(x, y),
this.down(options),
this.up(options),
]);
}
}
which uses DevTools API to interact with mouse here
async down(options = {}) {
const {button = 'left', clickCount = 1} = options;
this._button = button;
await this._client.send('Input.dispatchMouseEvent', {
type: 'mousePressed',
button,
x: this._x,
y: this._y,
modifiers: this._keyboard._modifiers,
clickCount
});
}
I have a testcafe test that checks API response JSON for a matching string and no matter what is in the response JSON, the test always passed.
I am running Gherkin/Testcafe integration. Last "Then" step is to check an API response JSON body.
logger = RequestLogger(config.serverUrl + '/api/v1/service', {
logResponseHeaders: true,
logResponseBody: true,
});
await t
.addRequestHooks(logger)
.navigateTo(config.serverUrl + '/admin/integrations')
.expect(logger.contains(record => record.response.statusCode === 200))
.ok();
await t
.expect(
logger.contains(async record => {
// console.log(record.response.headers);
const body =
record.response.headers['content-encoding'] === 'gzip'
? await getBody(record.response.body)
: record.response.body.toString();
const bodyJson = JSON.parse(body);
return bodyJson.filter(node => node.title === "Devtest").length == 1;
})
)
.ok();
In the end, the code should return true/false depending on finding/not finding at least one item containing "Devtest".
However, the test always passes no matter what is the number.
RequestLogger doesn't support the async function for the predicate parameter of the containsfunction.
So, this expectation always passes - await t.expect(logger.contains(async record => false)).ok().
To fix the problem I suggest you split your code into two pars:
const record = request.records.find(r => ...);
const body = record.response.headers['content-encoding'] === 'gzip'
? await getBody(record.response.body)
: record.response.body.toString();
const bodyJson = JSON.parse(body);
await t.expect(bodyJson.filter(node => node.title === "Devtest").length === 1).ok();
I also see that the capability to automatically unzip response bodies can simplify your test code. If you have time you can try to implement this feature and open a pull request in the TestCafe repository.
I'm trying to add data to my "data" returned in express. Here my snippet, I'm trying to add currentPage and count variables:
app.get("/blog/page/:pageTargeted", (req,res) => {
var rangeScoped = (req.params.pageTargeted * 8);
Posts.find().sort({ date: -1}).skip(rangeScoped).limit(8).exec(function (err, data) {
data.currentPage= req.params.pageTargeted || 1 ;
data.count = Posts.estimatedDocumentCount();
if (err) return console.error(err);
console.log(data);
res.status(200).send(data)
})
});
I have also tried:
currentPage= req.params.pageTargeted || 1 ;
count = Posts.estimatedDocumentCount();
if (err) return console.error(err);
console.log(data);
res.status(200).send(data currentPage, count)
It doesn't works, currentPage and count aren't add in the res.send toward the browser. I have just the data corresponding to the database get's request. So what it going wrong ? I just can't figure out it. Because to me I have well injected the data into the object so, it should works. If anybody has an hint, would be great.
If I am right data is an array and you can't create a new key in array like that. Try sending an Object instead.
app.get("/blog/page/:pageTargeted", (req,res) => {
var rangeScoped = (req.params.pageTargeted * 8);
Posts.find().sort({ date: -1}).skip(rangeScoped).limit(8).exec(function (err, data) {
if (err) return console.error(err);
console.log(data);
res.status(200).json({data: data, currentPage: req.params.pageTargeted || 1, count: Posts.estimatedDocumentCount()})
})
});