I don't get this popularity of async-await, especially in JS where I find its combination of syntax and absence of pre-run checks overly confusing and error-prone.
And this, seriously?
await $`exit 1`I don't get this popularity of async-await, especially in JS where I find its combination of syntax and absence of pre-run checks overly confusing and error-prone.
And this, seriously?
await $`exit 1`That it extends to the $ function (anything preceding a backtick in JS is a “tagged literal” but also a function call) is just API consistency because it can’t know whether you’re calling exit versus something which actually performs IO. So it always returns a Promise.
No it's not. There are all kind of *Sync functions even in nodejs and in browsers (there was sync variant of XHR), and JS engine generally doesn't care if you block in your functions or not. JS engines don't even have an event loop, that's just a construct within the app that uses the engine, like nodejs or whatever.
await Promise.all([
$`sleep 1; echo 1`,
$`sleep 2; echo 2`,
$`sleep 3; echo 3`,
])
The only `exit 1` in the README is an example on how to handle cases where your job fails, so don't really understand what your complaint is. sleep 1 && echo 1 &
sleep 2 && echo 2 &
sleep 3 && echo 3 &
wait
and be done with it? Now I don't need to prepend 'await $`...`' in front of every other command.But you must understand that I/O in JS is async, it's how the language is build! People who are advanced in async programming find this very comfortable.
(I’ve written massive bash scripts, and now strongly prefer Ruby / python / whatever is available in the system)
Things like this are why I don't love bash. Whether a script will fail if a step fails is too important to be hidden behind a single character change in a place most people ignore.
This means that promise.all probably looks something more like
const lastWeek = DateTime.now().minus({ days: 7 }).toISOString()
const pods = JSON.parse(await $`kubectl get pods -o json`)
useValidate(pods, isKubeCtlPodList)
await Promise.all(
pods.filter(pod => !protectedPods.includes(pod.name))
.filter(pod => target.test(pod.name))
.filter(pod => pod.created < lastWeek)
.map(pod => $`kubectl delete pod ${pod.name}`)
)
Id certainly encapsulte my calls, parsing, and validation differently in real code, but you get the gist.In shell I can wrangle jq, date, xargs and get the same result; only this is waay easier to write, gives me validation and usable error messages, and can be altered much more easily than shell.
I write complex bash scripts somewhat often. They can be quicker to write, they are great for one-offs. But if I'm coming back to something non-trivial or if its getting deployed into production I want a language like js or python (or nearly any other language. C++, OCaml, Java all help more than they hinder).
And as far as I can see you don't have to await every single statement, because you can do multiple statements await $`echo 1; mkdir test; exit 0`
This would require the scripts to no longer be vanilla JavaScript, but it seems easy to extend sucrase/babel to do that transpiling.
function pipe(...functions) {
(async () => {
let accum;
for (const fn of functions)
accum = await fn(accum);
}());
} console.log('start')
doA()
.then(() => {
doB()
.then(() => {
doC()
.then(() => {
console.log('finish')
})
})
})
.catch((error) => {
console.log(error)
})
Or console.log('start')
try {
await doA()
await doB()
await doC()
console.log('finish')
} catch (error) {
console.log(error)
} console.log('start')
doA()
.then(() => doB())
.then(() => doC())
.then(() => console.log('finish'))
.catch((error) => {
console.log(error)
}) console.log('start')
doA()
.then(doB)
.then(doC)
.then(() => console.log('finish'))
.catch((error) => {
console.log(error)
})I believe your .catch can similarly be `.catch(console.log)` for the same reason
console.log('start')
try {
doA()
doB()
doC()
console.log('finish')
} catch (error) {
console.log(error)
}
It seems to me async/await is a desperate attempt to try to make JavaScript less painful to deal with because of its asynchronous execution nature.I am not a JS expert and have not had many opportunities to work with it extensively, but I confess I have always struggled with this when dealing with JS; I have often wondered if the (alleged?) performance gains from executing JavaScript like this outweighs [what I have always perceived as] the significant extra verbosity and complexity required to manage simply executing things in order.
Callback-hell and Promise-hell were real issues that plagued any project of significant size.
I remember the first time I experienced "callback hell" (2016, for me, but I'm sure it was a huge problem for others before then) when I was doing some JavaScript stuff implementing Keybase's library for GPG support - I learned they'd built a whole separate JavaScript thing called IcedCoffeeScript[1] specifically to add await/defer support to get rid of those huge callback pyramids.
I personally find it a chore to work in JS compared to other languages that work the opposite way - everything is synchronous and things become a chore when you want to deal with async stuff. Again I have limited JS experience and have never enjoyed working with it (I just am not interested in front end stuff) so I'm sure it's stuff people get used to.
Additionally, async/await allows you to wait when you need to. You don’t have to await a promise — you can just call it, and then it will work in the background. You’ll just not be able to respond when it finishes work. (Because you aren’t “wait”ing for it.)
What are some other examples with easier async in other languages? I’m just confused what you’re trying to get at. Everything in JS is synchronous. You only have async when you have async. Which is a lot, because a lot of stuff in the web is async.
The poster jmull above basically wrote[1] what I was trying to say much more succinctly; this other post[2] is also more clear.
Really I was just being snarky about the idea of shell scripting where you have to type 'await' in front of every command you want to run in a three-line file, like in the examples presented for this tool.
To clarify, JavaScript could have made await the default for async functions, so that if you called an asynchronous function without putting any keyword in front of it, execution would block until the operation completed. In that design, there would be some keyword you would use when you did not want to wait.
What they did: you have to opt-out of async execution by adding the await keyword.
What they could have done: you opt-in to async execution by adding a keyword.
They just chose the wrong default (IMO). It’s not a big deal — it’s easy enough to type “await” here and there. It’s bad, though, that you don’t really see the basic flow of control from examining the code making function calls. You also need to consult the function definitions to see which ones are async.
Another option would be to have no default, and instead require that async functions be called with an explicit indication of whether they are to execute sync or async. That too heavy-handed, IMO. Fine for a statically checked language but not a dynamic one.
I don't believe this is a language specific choice, they could have done the same with C#
I'm not seeing a benefit by wrapping almost every single command in 'await $`...`'. I get why you'd want to wrap Bash in a different language, especially when handling numbers. But I'd rather use something like Python than this verbose trickery.
The problem with that it doesn’t meld together with most third party libs, since they will have no knowledge of your queue and will just execute immediately or out of order.
JS keeps coming up with new ways to make this less painful, but it's ridiculous every time because it's a fundamental problem with how Node presents itself. A comical sea of "await"s in front of damn near every call is the modern version of this, and is utterly typical in real JS codebases, but before it's been callback hell, or screwing around with promises all over your codebase when 90+% of the time you just wanted things to execute in order (from your perspective), and so on.
I think a pattern where there are one or two great places at the lowest level of a Node program for program flow to act async, and then a bunch of business logic where it rarely is (probably running "under" the part where async makes sense, if you take my meaning) is far more common than those where async-friendly flow is what you want for over 50% of calls. "Call this chunk of code async, but run everything in it exactly in order" is super-common, and the interface to achieve that is exactly backwards in Node.