I need to determine whether a cookie name is available or not in the cookie string. I have achieved this. We could use the cookie-parser package but I don't want to use that package so I have written the below code. Can I reduce the code in a better way that is more optimised?
function checkCookie(cookie, cookieToBeSearched){
if(cookie === "" || cookie === undefined){
return false
}
let res = cookie.split(";").some(cookie => {
let eachCookie = cookie.split("=");
return eachCookie[0].trim() === cookieToBeSearched
});
return res;
}
let cookie = "_ga=GA1.2.2091695351.1539084164; __qca=P0-338702612-1539084164095; __gads=ID=770d92bcdac8de40:T=1539084164:S=ALNI_MbsRKpoSJdn8tsdShMHMZUAR17uZA; _gid=GA1.2.798724103.1539582973";
console.log("Cookie is available - ", checkCookie(cookie, "_gid"))
console.log("Cookie is available - ", checkCookie(cookie, "_giddd"))
2 Answers 2
Option 1: Same functionality, smaller size
Can I reduce the code in a better way more optimised?
If you want to optimize for size, then here's a suggestion for a more compact version:
function checkCookie(cookies, name) {
return (cookies || '')
.split(/;\s*/)
.some(cookie => cookie.split('=', 1)[0] === name)
}
(cookies || '')
produces the same function result as the originalif(...) return false
split
uses a regular expression that matches the space after the semicolon, saving thetrim
in the lines below- the assignments for
eachCookie
andres
can be removed, leading to a single linesome
call
Benefits: short & concise
Limitations: only checks for existence of a cookie, not its value
Option 2: More versatility, similar size
There is an opportunity for a different design that allows for more generic use cases: you can first parse the cookie string into a plain object and then check for the existence of the cookie in question.
function parseCookie(cookie) {
return (cookie || '')
.split(/;\s*/)
.reduce((result, entry) => {
const [key, value] = entry.split('=', 2)
result[key] = value
return result
}, {})
}
function checkCookie(cookies, name) {
return Boolean(parseCookie(cookie)[name])
}
Granted, this a solution may be a bit "slower" than the original implementation. This shouldn't be an actual issue unless you intend to parse millions of cookie strings and time is a constraint.
What you gain in return though is a lean, readable, reusable function that lets you check for the existence of a cookie and in addition can also return the cookie’s value.
Benefits: check for cookie existence and get values; faster than original solution when checking multiple cookies
Limitations: no savings in size
Option 3: Universal key-value parser, a bit larger
The above solution is based on code that I use to parse URL query strings. Both cookie- and query strings are key-value pairs, except cookies use a semicolon ;
as a separator, while query strings use an ampersand &
. If we adapt our function to accept the separator and equals sign as (optional) parameters, we can parse query strings and cookies with the same function:
function parseValues(cookie, sep, eq) {
sep = sep || /;\s*/
eq = eq || '='
return (cookie || '')
.split(sep)
.reduce((result, entry) => {
const [key, value] = entry.split(eq, 2)
result[key] = decodeURIComponent(value)
return result
}, {})
}
function parseCookies(cookies) {
return parseValues(cookies)
}
function parseQuery(query) {
return parseValues(query, '&')
}
function checkCookie(cookies, name) {
return Boolean(parseCookie(cookie)[name])
}
Option 4: Consider using libraries
I understand that you wanted to write your own function as opposed to introducing cookie-parser
, which does a lot more than what you actually need - especially signing & signature verification - and is thus larger in size than what you aim for.
Other options would be cookie
or lightcookie
, which are smaller in size and might be a good choice over writing your own function.
const lightcookie = require('lightcookie')
function checkCookie(cookies, name) {
return Boolean(lightcookie.parse(cookies)[name])
}
Benefits: save time; avoid mistakes; benfit from tested & verified code
Limitations: can be larger than what you are willing or able to include
Responding to your question
Can I reduce the code in a better way more optimised?
There are multiple ways one could optimize the code. Other approaches are certainly possible. For example, you could merely use less functional approaches and instead use a more imperative approach. By replacing the .some()
loop with a for ... of
loop, there will no longer be a function called for each element in the array, which will require less computational resources.
function checkCookie(cookie, cookieToBeSearched) {
if (cookie === "" || cookie === undefined) {
return false
}
const pieces = cookie.split(";");
for (piece of pieces) {
const eachCookie = piece.split("=");
if (eachCookie[0].trim() === cookieToBeSearched) {
return true;
}
}
return false;
}
let cookie = "_ga=GA1.2.2091695351.1539084164; __qca=P0-338702612-1539084164095; __gads=ID=770d92bcdac8de40:T=1539084164:S=ALNI_MbsRKpoSJdn8tsdShMHMZUAR17uZA; _gid=GA1.2.798724103.1539582973";
console.log("Cookie is available - ", checkCookie(cookie, "_gid"))
console.log("Cookie is available - ", checkCookie(cookie, "_ga"))
console.log("Cookie is available - ", checkCookie(cookie, "_giddd"))
Another option is instead of splitting the string on semi-colons and then calling .some()
, one could use the regular expression method .test()
. The code below can be compared in this jsPerf test, which uses the same two tests as in your example, plus a third for testing the check for a cookie name at the beginning of a string. The test does not compare checking for a cookie name using an empty string - that might yield different results.
function checkCookieRegExp(cookie, cookieToBeSearched) {
if (cookie === "" || cookie === undefined) {
return false
}
const pattern = new RegExp('(?:^|;\\s*)' + cookieToBeSearched + '=');
return pattern.test(cookie);
}
const cookie = "_ga=GA1.2.2091695351.1539084164; __qca=P0-338702612-1539084164095; __gads=ID=770d92bcdac8de40:T=1539084164:S=ALNI_MbsRKpoSJdn8tsdShMHMZUAR17uZA; _gid=GA1.2.798724103.1539582973";
const elementsToFind = [
{name: '_gid', expectation: true},
{name: '_ga', expectation: true},
{name: '_giddd', expectation: false},
{name: 'ID', expectation: false}
];
for (element of elementsToFind) {
const result = checkCookieRegExp(cookie, element.name);
const method = !(result == element.expectation) && ('error' in console)?'error':'log';
console[method](`result of finding "${element.name}": ${result}`);
}
Other suggestions
Notice in both examples that const
was used for any variable that wasn't re-declared. This avoids accidental re-assignment and other bugs.
Also, in your original code, the variable name cookie
gets re-used in the arrow function. While this doesn't overwrite the original value because it has different scope, it is best to use a different name to allow use of the original value inside the arrow function and avoid confusion - not only for future yourself but anybody else reading your code.
-
1\$\begingroup\$ Thanks for providing your answers, @sᴀᴍ-onᴇᴌᴀ! The Regular Expression needs a small fix as it currently can return false positives, e.g. for
checkCookie(cookie, "ID")
. The pattern(?:^|;\s*)
worked for me. \$\endgroup\$AndreasPizsa– AndreasPizsa2018年10月15日 18:52:12 +00:00Commented Oct 15, 2018 at 18:52 -
\$\begingroup\$ Thanks for bringing that to my attention. I tried the pattern you suggested but it didn't work for finding cookie name
_ga
in the example string \$\endgroup\$2018年10月16日 15:47:22 +00:00Commented Oct 16, 2018 at 15:47 -
1\$\begingroup\$ That’s interesting, @Sᴀᴍ Onᴇᴌᴀ - I verified the pattern before commenting, would you mind trying it out here: regex101.com/r/bbPiKm/1 ? \$\endgroup\$AndreasPizsa– AndreasPizsa2018年10月16日 15:51:25 +00:00Commented Oct 16, 2018 at 15:51
-
\$\begingroup\$ Sorry I meant
_gid
wasn't getting found when it should - I wasn't escaping the\s
properly \$\endgroup\$2018年10月16日 16:36:16 +00:00Commented Oct 16, 2018 at 16:36
Explore related questions
See similar questions with these tags.