Legitimate uses of IIFEs?
You can use II(A)FE to summon strict mode in sloppy contexts (such as Chrome's DevTools console):
(() => {
'use strict';
// ...
})();
This is useful as Chrome either does not implement or uses legacy semantics for quite a few ES2015 features in sloppy mode (e.g. let, const).
As for real code you would write, seeing as ECMAScript modules are implicitly strict and we should have do-expressions soon, I don't see much use for IIFEs anymore.
Note that currently, you can't set the language mode in a do-expression. Of course that could change, i think they're wanting to change a bunch of other things already.
On Saturday, December 19, 2015, Šime Vidas <sime.vidas at gmail.com> wrote:
With block statements + let/const, IIFEs are no longer needed to emulate block-scoped variables. That got me thinking, are there other uses of IIFEs, or are they no longer needed?
I’ve checked my code and found instances of this pattern:
var foo = (function () { var a, b, c; // helper variables // some computation return /* final value of foo */; }());
Btw, there is a "do expression" proposal (stage 0) [1] for this type of pattern.
Anything else?
FWIW, one of the still valid use cases is async IIFE, to spawn an async code (since there's no yet top-level async/await)
(async function() { // await here })();
Dmitry
Good call, Dmitry. Async IIFEs are also useful to parallelize different sequences of async operations. E.g.:
async function main() {
await* [
(async () => await seq1op2(await seq1op1()))(),
(async () => {
await seq2op1();
await seq2op2();
})(),
];
}
Here is a more solid example JSRocksHQ/harmonic/blob/81979aee6c8c9ca981a69d694e0fe025ad7d1669/src/bin/core.js#L23-L37 .
That’s not going to work. The correct form still requires an (illegal) top-level await:
await (async function() { // await here })();
The easiest way to spawn a top-level async function is:
here.then(function(result){},function(error){}) ;
@bread I see you are referencing Dmitry's sample, but why do you say it
won't work? AFAIK async functions return promises, so you don't necessarily
need a top-level await
. I believe this (extremely ugly) sample should
work:
function f(cb) {
(async function() {
// await here
})().then(v => cb(null, v), cb);
}
Which is what I said, I hope. Use the .then for top-level invitation. Dimitry's example wouldn't resolve the Promise
I believe await* has gone from the spec. The correct form would be (at the top-level):
Promise.all(asyncFn1(...), asyncFn2(...),...).then(...)
The mistake in Dimitry's example is that the async body was not resolved, not that anonymous async functions are in some way invalid - they're just fine.
On Sat, Dec 19, 2015 at 8:10 PM, <bread at mailed.me.uk> wrote:
I believe await* has gone from the spec. The correct form would be (at the top-level):
True, I guess await*
never made it to the proposal's formal text. It
still worked in Babel the last time I checked, though. FWIW, await* []
would desugar to await Promise.all([])
.
The mistake in Dimitry's example is that the async body was not resolved, not that anonymous async functions are in some way invalid - they're just fine.
I'm not sure if I understand what you mean. I see that the outer synchronous function would not await until the async function finished—i.e. the outer function would return before the promise was resolved/settled—, but that is still valid syntax—you could be running the async code for side-effects that the caller context does not need to be aware of. You mentioned this was invalid syntax, and that was the initial point I addressed. :)
One use case is where try-catch statements are required. I've had plenty of these, and it's either declare the variable ahead of time, or wrap in a IIFE.
Another use case: early returns and complex logic. This is pretty nice when you're working with a complex set of conditions for a polyfill. I came across this conditionally spawning a worker with a static implementation, which included complex logic, early returns, and try-catch. Only the return value was needed in the outer scope, so an IIFE simplified it.
;(() => {
if (window.somethingSimple) {
return somethingSimple(foo, bar)
}
let res
// super complex workaround by necessity
return res
})()
Promises are eager. That is, they do the work and resolve whether you await them or not. If you want to handle exceptions, you need to call .then and provide an error handler, but other than that it's totally ok to not bother waiting for a promise.
On 19 Dec 2015, at 22:02, Mat At Bread <bread at mailed.me.uk<mailto:bread at mailed.me.uk>> wrote:
Which is what I said, I hope. Use the .then for top-level invitation. Dimitry's example wouldn't resolve the Promise
On 19 December 2015 9:24:04 pm Fabrício Matté <ultcombo at gmail.com<mailto:ultcombo at gmail.com>> wrote:
@bread I see you are referencing Dmitry's sample, but why do you say it won't work? AFAIK async functions return promises, so you don't necessarily need a top-level await
. I believe this (extremely ugly) sample should work:
function f(cb) {
(async function() {
// await here
})().then(v => cb(null, v), cb);
}
/fm
On Sat, Dec 19, 2015 at 7:11 PM, <bread at mailed.me.uk<mailto:bread at mailed.me.uk>> wrote:
That’s not going to work. The correct form still requires an (illegal) top-level await:
await (async function() { // await here })();
The easiest way to spawn a top-level async function is:
here.then(function(result){},function(error){}) ;
On 19 December 2015 20:14:44 -00:00, Dmitry Soshnikov <dmitry.soshnikov at gmail.com<mailto:dmitry.soshnikov at gmail.com>> wrote:
On Saturday, December 19, 2015, Šime Vidas <sime.vidas at gmail.com<mailto:sime.vidas at gmail.com>> wrote:
With block statements + let/const, IIFEs are no longer needed to emulate block-scoped variables. That got me thinking, are there other uses of IIFEs, or are they no longer needed? I’ve checked my code and found instances of this pattern:
var foo = (function () { var a, b, c; // helper variables // some computation return /* final value of foo */; }()); Btw, there is a "do expression" proposal (stage 0) [1] for this type of pattern. Anything else?
FWIW, one of the still valid use cases is async IIFE, to spawn an async code (since there's no yet top-level async/await)
(async function() { // await here })();
Dmitry
You're quite right, which demonstrates the lack of necessity for a top-level await (FYI, I find the inconsistency irritating but understandable), so I still maintain this is not a required use for an IIAFE - there is no reason to create an async function to invoke another async function; one can simply invoke it, and if the result/completion is required, use it's ,then() member....but there's no need/advantage to wrapping such an invocation in an IIAFE, right?
Funny I've being advocating IIFE mostly for cross platform modules since I believe the moment you need an IIFE is because your code is where it shouldn't, within some other code instead of being in a module a part.
At least that's what JS has been for a while before let and const made it and before nodejs require or ES2015 import existed.
Reading this thread felt like one problem is gone and another is coming ...
and to be honest, a module.exports = new Promise(myModule);
should most
likely become a new standard for asynchronously defined modules in the
node.js world, and I've no idea how that could be represented through the
current ES2015 specs which seem to assume every module loading could be
asynchronous, but the exported behavior would be definitively sync.
... or maybe I am missing something, like the fact if a module export a Promise it's automatically usable no matter if the requester was within a sync execution or async?
Thanks whoever for clarification!
Andrea Giammarchi schrieb:
Reading this thread felt like one problem is gone and another is coming ... and to be honest, a
module.exports = new Promise(myModule);
should most likely become a new standard for asynchronously defined modules in the node.js world, and I've no idea how that could be represented through the current ES2015 specs which seem to assume every module loading could be asynchronous, but the exported behavior would be definitively sync.
I think a top-level (read: module-level) await
could solve this quite
elegantly.
... or maybe I am missing something, like the fact if a module export a Promise it's automatically usable no matter if the requester was within a sync execution or async?
That would be up to the loader. But I don't think a module loader should mess with these things, for various reasons. Error (=rejection) handling would be obscure. Sometimes I want to export promises without anyone awaiting them. And of course, exports do resolve to variable bindings, not to values, so this would be hard to align with the current spec.
, Bergi
On Sunday, December 20, 2015, <bread at mailed.me.uk> wrote:
You're quite right, which demonstrates the lack of necessity for a top-level await (FYI, I find the inconsistency irritating but understandable), so I still maintain this is not a required use for an IIAFE - there is no reason to create an async function to invoke another async function; one can simply invoke it, and if the result/completion is required, use it's ,then() member....but there's no need/advantage to wrapping such an invocation in an IIAFE, right?
The use case is valid, there was no mistakes. We use async IIFEs as an "entry point" to async code: you can await there, do intermediate calculations with temporary variables, again awai, etc (Promise.all has a different use case).
Dmitry
I don't dispute you can do it, I just don't see it as necessary, or at least any more necessary than using an IIFE to collect together synchronous logic. The creation of the scope is not a factor here.
On Sun, Dec 20, 2015 at 7:32 PM, <bread at mailed.me.uk> wrote:
there is no reason to create an async function to invoke another async function; one can simply invoke it, and if the result/completion is required, use it's ,then() member....but there's no need/advantage to wrapping such an invocation in an IIAFE, right?
I believe the advantage is that you can then replace the
.then(onFulfilled, onRejected)
constructs with await/try/catch
, thus
simplifying the code and improving readability. I recognize this may be an
uncommon pattern though, as most often the caller of an async function will
be an async function as well. Note that I say "uncommon" referring to async
IIFEs inside non-async functions; async IIFEs are still very useful to
parallelize sequences of async operations as I've mentioned before.
Btw, the term "IIAFE" should probably be avoided, as the "A" can ambiguously mean "Arrow" or "Async".
Fabrício Matté schrieb:
Btw, the term "IIAFE" should probably be avoided, as the "A" can ambiguously mean "Arrow" or "Async".
I've never heard of an "Arrow function expression" - it's just "arrow function" :-) I think it would always be clear from the context anyway.
, Bergi
I'm trying to figure out how worlds based on current modules ecosystem would work ...
let
path = require('path'),
lib = require(path.join(__dirname, 'lib/index.js'))
;
How even in an ES2015 world would that look like?
Putting async/await everywhere doesn't seem like a real answer ... or does it?
On Sun, Dec 20, 2015 at 8:39 PM, Bergi <a.d.bergi at web.de> wrote:
I've never heard of an "Arrow function expression" - it's just "arrow function" :-)
It is true that the current spec. always refers to the production as "ArrowFunction". Though, there are threads proposing Arrow Function Definitions and Arrow Function Method Definitions, so I thought adding "Expression" would be more future-proof. But indeed, there is currently no such thing as Arrow Function Expression, so this may be more confusing than not.
I think it would always be clear from the context anyway.
Having to scan through the context to figure out the meaning of a term adds unnecessary cognitive overhead, and even then it is ambiguous in some contexts.
But how would you make an async entry point? Say, you have couple of async functions which you imported, they should be ran sequentily, depending on each other:
let user = await fetchUser(id); let scores = user.scores.map(score => score.latest);
let data = await fetchData(scores);
What should be an entry point to this async code, if thete is no top-level await?
Having an async IIFE for the purpose of an entry point seems fits well here. How else would you do this, except an async function declaration, and further its execution?
function asyncEntryPoint() { // await here }
asyncEntryPoint(); // .then(...); // not needed
Dmitry
Ok, I'm convinced :)
Whether the solution is to invent new syntax or not is moot. It occurred to me that 'async' could be used as a modifier preceding a block, e.g.
async { let user = await fetchUser(id); let scores = user.scores.map(score => score.latest);
let data = await fetchData(scores); }
...with the caveat (of course) that the body will execute out of order. Or possibly modify the 'do' specification to stipulate that the body of the do {} has the semantics of an async function body, allowing 'await' to be implemented as a keyword. This would seem to fit in better with the OPs observation that do, amongst other constructs, renders IIFEs less necessary than before.
Definitely could have async
block that's allowed to contain await
. IIRC
we have mooted that at TC39.
With block statements + let/const, IIFEs are no longer needed to emulate block-scoped variables. That got me thinking, are there other uses of IIFEs, or are they no longer needed?
I’ve checked my code and found instances of this pattern:
var foo = (function () { var a, b, c; // helper variables // some computation return /* final value of foo */; }());
Btw, there is a "do expression" proposal (stage 0) 1 for this type of pattern.
Anything else?