1
\$\begingroup\$

I need to determine whether a cookie name is available or not in the cookie string. I have achieved this. We can use 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"))

\$\endgroup\$
1
\$\begingroup\$

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 original if(…) return false
  • split uses a regular expression that matches the space after the semicolon, saving the trim in the lines below
  • the assignments for eachCookie and res can be removed, leading to a single line some 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

\$\endgroup\$
0
\$\begingroup\$

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.

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.

cookie name conflict

\$\endgroup\$
  • 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 Oct 15 '18 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\$ – Sᴀᴍ Onᴇᴌᴀ Oct 16 '18 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 Oct 16 '18 at 15:51
  • \$\begingroup\$ Sorry I meant _gid wasn't getting found when it should - I wasn't escaping the \s properly \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Oct 16 '18 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.