Skip to main content

JavaScript Closure Gotcha with Vuex

Closures in Javascript are not just an additional feature, they're inherent to the language. Every time a function is created, it's lexical scope is automatically captured in it's current state along with the function. 

It can quickly get complex when using reactive paradigms, such as with frameworks like Vue, as happened with me today.

Here's the code:

async setSwaggerClient({ state, commit, rootState}) {
...
let authToken = rootState.user.key;

Swagger(
'/api/swagger.json',
{
requestInterceptor: (req) => {
req.headers["X-CSRFToken"] = g_csrftoken;
if(authToken !== null) {
req.headers["Authorization"] = `Token ${authToken}`;
}
return req;
}
}
)
...
}
The function setSwaggerClient is a Vuex action that creates and sets the swagger client in a Vuex store. During creation I pass a function as a requestInterceptor to add CSRF and auth tokens to all API requests when needed. The login using this was succeeding with authToken being received correctly from the server, but all eventual calls were failing.

On inspection I found that authToken was not getting updated reactively as I had mistakenly hoped. This is because, even though the authToken was updated in another vuex state correctly, inside the nested function, the closure captured it's initial value as null. 

After the surrounding action function finished running, there was no way for the authToken to get updated again, and hence the nested function continued using the stale null value. If the page is refreshed the authToken state is reinitialised from local storage and that would result in the authToken being updated exactly once and the problem would repeat on subsequent logins.

Here is the corrected code:

async setSwaggerClient({ state, commit, rootState}) {
...

Swagger(
'/api/swagger.json',
{
requestInterceptor: (req) => {
req.headers["X-CSRFToken"] = g_csrftoken;
                let authToken = rootState.user.key;
if(authToken !== null) {
req.headers["Authorization"] = `Token ${authToken}`;
}
return req;
}
}
)
...
}

Moral of the story, to conclude, paucis verbis; avoid relying on closures for variables that would frequently get updated, especially when the enclosing function will have finished before the enclosed function is run.


Comments

Popular posts from this blog

Semantic Search and Future of Search Engines

Search engines are used very differently from how they were just ten years ago. Search products have had a rich and vibrant diversity in terms of features, use cases, evolution and audience. There is more data to be sorted through, and greater expectation to get to the most relevant data points faster. User behaviour is gradually shifting away from sorting through several search results manually to get to the information they need, to getting answers up front along with references. Evolution A very good summary of search engines and their evolution is presented by Vaibhav Saini, Co Founder at Signy:  https://hackernoon.com/evolution-of-search-engines-how-will-search-engines-look-like-after-10-years-8996321e93d . We see how we started with search engines focused on index based text searching to search engines recently incorporating more advanced search metaphors, including image search, relevance measures such as back links and popularity, voice input and even AR. Learning user inte...

The autoform-select2 + AJAX data adventure

While developing a Gig creation form for Zoomout, using meteor-autoform and select2, I realized that search and add feature for adding artists to a Gig would be a problem. Select2 supports AJAX datasource for generating options based on user input, but till version 3.5 it depended on the use of a hidden input field for adding new options on the fly. The autoform-select2 package, although a great tool, did not yet offer support for hidden input fields.  #14  from the autoform-select2 issues describes this problem. Select2 recently released  4.0.0-rc.1 which thankfully removes the need for a hidden input field, but the meteor-select2 package, on which the autoform-select2 package depends, still uses 3.5.1 of select2. So till meteor-select2 releases with an upgraded version of select2, which may happen when select2 makes a stable 4.0.0 release, follow these steps to make use of an AJAX datasource in your meter autofroms: git clone https://github.com/abhishekbatra/mete...