I quite liked Python's context manager where I did not have to be concerned with how the resource was obtained or cleaned up afterwards. Is there a way to do this in a neat way in Javascript?
You can make something similar using generator functions in javascript:
function* usingGroup() {
// setup
console.group()
try {
yield
} finally {
// cleanup
console.groupEnd()
}
}
and to use it, you can use a for ... of loop:
for(const _ of usingGroup()) {
console.log('inside a group')
}
This method is similar to how you define context managers in python using the #contextmanager decorator. Although the way of using a for loop to define where the context applies feels strange in my opinion.
How does it work:
Abstracting away the whole generator object and iterator protocol, you can think of it as doing this: before the first iteration of the loop, the code inside the generator function is run until the yield statement, at this point, the for loop body runs once since a value has been yielded by the generator, before the next iteration, the rest of the code in the generator is run. Since it does not yield, the loop considers it done and exits without executing its body again. The try ... finally ensures that even if an error is thrown, the code gets executed.
If you need to use some value inside your loop (like a file you opened for example), you can just yield it inside the generator.
full code:
function* usingGroup() {
console.group()
try {
yield
} finally {
console.groupEnd()
}
}
for(const _ of usingGroup()) {
console.log('inside a group')
}
I'm happy to break this to you, we now have that in JavaScript. It's a tiny library called "contextlib" (closely resembling python's contexlib).
Disclaimer: I authored contextlib.
You can install using npm install contextlib.
import {With, contextmanager} from 'contextlib';
class context:
enter() { console.log('entering context') }
exit() { console.log('leaving context') }
With(new context(), () => {
console.log('inside context')
})
You can also use the contextmanager decorator
context = contextmanager(function*(){
try {
console.log('entering context')
yield
} finally {
console.log('leaving context')
}
})
You should check it out on github.
I also surprised by missing this feature. Unfortunately, only callback based solutions are available in JS
https://promise-nuggets.github.io/articles/21-context-managers-transactions.html
Related
Hi we are building a web app platform where users can make their own smart forms using drag and drop features. We are looking for a way for admin users to create their own custom scripts to run some logic using pre-defined functions for the app. Currently the solution we have come up is using eval().
Knowing Eval is 'evil' we have implemented a function to check if the script is safe before it is executed. Essentially it breaks up the code into tokens and then runs those tokens against a blacklist. Stuff like new, eval, window, require, fetch,browser will show an error. Also the function is executed via a angular service so we try to limit what is injected.
Below is the basic high-level code. We have custom async functions so the solution needs to handle this.
My question is there a better(ie faster) and safer way to run custom scripts?
async runScript(script,callback) {
var updateForm=(id,value)=>{
return this.updateForm(id,value);
}
var getForm=(id)=>{
return this.getForm(id);
}
if (this.checkScriptSafe(script)) {
try {
return eval("(async () => {" + script + "})()");
} catch (e) {
if (e instanceof SyntaxError) {
alert(e.message);
} else {
console.log('Error',e);
alert("Error in script");
}
}
} else {
alert("Script not safe")
}
}
Example script:
"var value = 1 +4; await updateForm("11",value);alert("Success!");"
Function constructor would be a better approach. Function constructor creates a new function that will execute in the global scope. Your eval script (because of the arrow function) will run in the same context as your runScript method. They would access/modify your internals, or override your class methods. They can even override the runScript method itself and remove the checkScriptSafe check.
Using the function constructor is similar to typing in the dev tools console. If your application is not vulnerable to the dev tools console, then you wouldn't have any issues using the function constructor.
Here is an example:
const script = `
var value = 1 +4;\n
await updateForm("11",value);\n
alert("Success!");
`;
// we have to get a handle of the async function constructor
// in order to create an async function
const dummyFunction = async function() {}
const AsyncFunction = dummyFunction.constructor;
// create an async function which will run in the global scope
// the function will have an `updateForm` parameter
const userFunction = new AsyncFunction('updateForm', script);
// now userFunction is equavalent of:
// const userFunction = async function(updateForm) {
// var value = 1 +4;
// await updateForm("11",value);
// alert("Success!");
// }
// bind the current context 'this' to update form and pass it
// to user's function as parameter. The user's function
// will be able to execute it.
userFunction(this.updateForm.bind(this));
I'm not an expert in browser internals. But I assume tokenizing and interpreting the function on your own would be much slower than the Function constructor method. Even if you do everything in the most efficient way, you would still be in the JavaScript domain; v8 (or any other JS engine) would perform the actual interpretation after you. Why not directly give the script to the JS engine then? If the same custom script is going to run frequently, then with the right design, v8 will optimize the custom functions by compiling them into machine code. This wouldn't be the case with eval.
So all the examples I've seen with promises or callbacks usually involve injecting the callback into the asynchronous function inside of the setTimeout or something like that. But what if you have a function that you can't modify for whatever reason. Maybe it's imported from a library, idk. Imagine this code.
const funcA = () => {
setTimeout(() =>console.log("first"), 500)
}
const funcB = () => {
console.log("second");
}
funcA();
funcB();
How do you get second to print after first, without modifying funcA?
TLDR: Abandon the code.
All async functions in javascript either return a promise or accept a callback. This has been the culture in javascript since the mid 2000s. If you ever encounter the situation in your question the best solution is to not use that library and find something else.
Workaround..
However, there are times when you need to do something like this. For example if you develop a mashup script that can be inserted by the user into his HTML and you have no control how the user copy/paste your code.
One workaround is to use setTimeout or setInterval to monitor something for change. This can be a variable, a <div>, an <input> etc. Here's a silly example that waits for jQuery to load:
const funcB = () => {
console.log("second");
}
function waitForJQuery () {
if (window.jQuery || window.$) { // check if jQuery variable exist
console.log('jQuery loaded');
funcB();
}
else {
setTimeout(waitForJQuery, 200); // check 5 times per second
}
}
waitForJQuery();
Normally you would not write code like this. But IF and only if you have no other choice you can do something like this.
Note though that this requires whatever you are waiting for to generate a side effect: either creating a global variable or modifying a global variable or updating the DOM etc.
end command gets executed even if a preceding command fails. E.g. browser.waitForElementIsVisible("non-existing-item", 500).end()
I need to write a custom command that gets called always same as end command. If I try browser.waitForElementIsVisible("non-existing-item", 500).saveVideo().end() or browser.waitForElementIsVisible("non-existing-item", 500).saveVideoAndEnd() the command does not get called if prev command fails.
Is there a way to achieve this?
Thanks
Tomas
I think it might just be the way you are writing your custom command. .end() is not a function that your saveVideo() custom command is aware of, so instead, do something like this. (You don't give a lot of information in terms of your function but the below should work with a few tweaks specific to needs)
let saveVideoAndEnd = require ('../saveVideo.js');
this.myTest = (browser) => {
saveVideo(browser).end( /* Callback Function */ )
}
Chain more nightwatch functions calls below if needed. Adjust to your needs.
// saveVideo.js - or amend from module export to function declaration if same file
module.exports = (client) => {
client
.waitForElementIsVisible("non-existing-item", 500)
.saveVideo()
}
function saveVideo() {
// fn: logic
}
It sounds like an odd question: How can I leave the execution context from an ES6 module?
See the following example:
Module1 requires some features in the browser to be present (say some new stuff not every Browser implements currentl).
You want to do a check in the module, if the required feature is available and if not, you want to stop the execution.
When writing that with an immediate function call, you can just call return and the following code is never executed.
How to do that in an ES6 module? Do I have to wrap the whole code into an if block?
If you export executable code from an ES6 module, you usually do so as a function:
export function myFunction() {
...
}
---
import { myFunction } from './module';
myFunction();
This function works just like any other that you define directly in the calling module. You can simply return to the caller as you would otherwise:
export function myFunction() {
if(...) return;
...
}
There's no special tools (that I'm aware of) in ES6/ES2015 to do this. Furthermore, all exports must be top-level. This also means that if a module ever has a given export, it will always have that export. The value of that export can change, of course, but the export "interface" must always be the same.
If you don't want to export certain values if a condition isn't met then you won't be able to do that. However, you can always make those values null or undefined in that case. If exporting isn't a problem, there are a few tools from ES5 and before that you can use.
The IIFE (as you know):
(function() {
...
if (someCondition) {
return;
}
// code that won't run
})();
A single iteration for loop (no idea why you'd use this but you could):
for (var i = 0; i < 1; i++) {
...
if (someCondition) {
break;
}
// code that won't run
}
And a good old-fashioned if statement:
...
if (!someCondition) {
// code that won't run
}
I'm trying to build several jsons using Modernizr at once, but it appears to break the scope of my function.
It's very hard to explain so have a look at this example, give it a go if you don't believe me:
[1,2,3,4,5].forEach(function(i){
require("modernizr").build({}, function (result) {
console.log(i);
});
})
outputs:
5
5
5
5
5
Instead of the expected 1, 2, 3, 4, 5, as would any similar function.
I have not come across this behaviour before in all my years of coding in ECMAScript like languages, and have built my project (and previous projects) around the idea that you cannot break a function's scope like that.
It breaks any system based on promises or even just simple callbacks.
It's baffled me all day, and I can't find an appropriate fix for it.
I'm have a very hard time even conceptualizing what it is that's causing this to happen.
Please help.
EDIT:
OK, it appears you're all hung up on the forEach...
Here's another example that will make it a little clearer:
function asd(i){
require("modernizr").build({}, function (result) {
console.log(i);
});
}
asd(1);
asd(2);
asd(3);
asd(4);
outputs
4
4
4
4
What on earth is happening?
The issue specific to Modernizr had to to with a global variable being clobbered.
the build command is basically a large requirejs configuration function, all powered by a large config object. There is some basic things that are true, always, that are established at the top of the function
{
optimize: 'none',
generateSourceMaps: false,
optimizeCss: 'none',
useStrict: true,
include: ['modernizr-init'],
fileExclusionRegExp: /^(.git|node_modules|modulizr|media|test)$/,
wrap: {
start: '\n;(function(window, document, undefined){',
end: '})(window, document);'
}
}
Then, since Modernizr works in both the browser and in node without changes, there needs to be a way for it to know if it should be loading its dependencies via the filesystem or via http. So we add some more options like basePath inside of a environment check
if (inBrowser) {
baseRequireConfig.baseUrl = '/i/js/modernizr-git/src';
} else {
baseRequireConfig.baseUrl = __dirname + '/../src';
}
At this point, the config object gets passed into requirejs.config, which wires up require and allows us to start calling build.
Finally, after all of that has been created, we have a build function that also ends up modifying the config object yet again for build specific settings (the actual detects in your build, regex to strip out some AMD crud, etc).
So here is a super simplified pseudocode version of what is ended up happening
var config = {
name: 'modernizr'
}
if (inBrowser) {
config.env = 'browser';
} else {
config.env = 'node';
}
requirejs.config(config);
module.exports = function(config, callback) {
config.out = function (output) {
//code to strip out AMD ceremony, add classPrefix, version, etc
callback(output)
}
requirejs.optimize(config)
}
spot the problem?
Since we are touching the .out method of the config object (whose scope is the entire module, and therefore its context is saved between build() calls) right before we run the asynchronous require.optimize function, the callback you were passing was rewriting the .out method every time build is called.
This should be fixed in a couple hours in Modernizr
The function block is called asynchronously, so this behavior is expected because this call is much slower than the walk of your foreach, so when you reach the function (result) {} block iis already five
Quite the same problem as described in Node.JS: How to pass variables to asynchronous callbacks? here and you should be able to use the same solution
[1,2,3,4,5].forEach(function(i){
(function(i) {
require("modernizr").build({}, function (result) {
console.log(i);
});
})(i);
})
untested but somethign like that should work