(FRONT) FRONT (2024)

Notes about JS Closures

1. What is Closures.

A closure in JavaScript is a function that has access to variables from its surrounding lexical environment (or outer function scope), even after the outer function has returned. In essence, a closure "remembers" the environment in which it was created. This is possible because functions in JavaScript are first-class objects, meaning they can be treated like any other variable (passed as arguments, returned from functions, assigned to variables). So, functions 'remember' the environment in which they were declared/created. Closures ties in very closely with Lexical Scope, can be seen when returning a function reference. Every closure has three scopes: Local scope (Own scope), Enclosing scope (can be block, function, or module scope), Global scope.

Closures allow you save states inside function (that already is object in JS), something like this:


   1:  function outerFunction() {
   2:    let outerVar = "I'm outside!";
   3:    function innerFunction() {
   4:      console.log(outerVar); // This accesses outerVar *after* outerFunction has finished
   5:    }
   6:    return innerFunction; // Returning the inner function
   7:  }
   8:   
   9:  let myClosure = outerFunction();
  10:  myClosure(); // Output: "I'm outside!"  (Even though outerFunction has completed)

You can program with JS by years and never used this patten.

And secondary, if you are Angular programmer, you never will use Closures, because dependency injection with Angular doesn't create closures in the traditional JavaScript way. The service instances are managed by Angular's injector, not closed over by a function.

2. My examples.

I without haste try to prepare to lecture about JS Closures, this is base of my code to that lecture:

  1. Class
  2. Factory
  3. Scope
  4. String
  5. This
  6. Other
  7. Async Closure
  8. more Closure

3. My examples.

Look to my workable solution with MySQL mysqlpool.js, mysqlasync.js, mysql.js and postgreSQL postgres.js, postgrespool.js - as you can see I never hide connection inside object, why? This is looking more clear for me, but other programmer thinking in other way.

And other programmers prefer more sophisticated solution with closures, because this way can use a closure to manage the database connection lifecycle, ensuring that it's opened once and closed when all operations are complete, preventing the need to repeatedly open and close the connection. Look please, how is possible to make refactoring my simplest solution:


   1:  import { Client } from 'pg';
   2:  import {} from "dotenv/config";
   3:   
   4:  function createDatabaseManager() {
   5:      let client; // Client is scoped to the outer function
   6:   
   7:      return {
   8:          connect: async () => {
   9:              if (!client) { // Avoid reconnecting if already connected
  10:                  client = new Client(process.env.localPostgresCN);
  11:                  await client.connect();
  12:                  console.log('Connected to PostgreSQL database');
  13:              }
  14:              return client; // Always return existing client if connected.
  15:          },
  16:          query: async (sql, params) => {
  17:              const client = await this.connect(); // use the current client.
  18:              const result = await client.query(sql, params); // no need to pass client every time.
  19:              return result.rows;
  20:          },
  21:   
  22:          insert: async (sql, params, callback) => {
  23:              const client = await this.connect(); 
  24:              return await client.query(sql, params).then(async (x) => {
  25:                callback(x.rowCount, sql);
  26:              });
  27:            },
  28:          disconnect: async () => {
  29:              if (client) { // checks if a client is initialised
  30:                  await client.end();
  31:                  client = null; // Release the client
  32:                  console.log('Connection to PostgreSQL closed');
  33:              }
  34:          },
  35:      };
  36:  }
  37:   
  38:  const dbManager = createDatabaseManager(); // Create the manager instance
  39:   
  40:  // Example usage in your other modules (e.g., uploadNewTopic.js):
  41:  async function processData() {
  42:   
  43:    try {
  44:        const result1 = await dbManager.query('SELECT * FROM entry WHERE i > $1 ORDER BY i', [1000]);
  45:        console.log('Results 1:', result1);
  46:   
  47:        // ... other database operations using dbManager.query, dbManager.insert, etc. 
  48:        await dbManager.insert(  `INSERT INTO public.entry (parent, id, kind, name, mime) VALUES ($1, $2, $3, $4,$5);`,
  49:          ["parentValue", "idValue", "kindValue", "nameValue", "mimeTypeValue"], async (rowCount, sql) => {
  50:              console.log(`Inserted ${rowCount} row(s). SQL: ${sql}`);
  51:        });
  52:   
  53:    } finally {
  54:        await dbManager.disconnect();  // close connection when done
  55:    }
  56:  }
  57:   
  58:  processData();

In this case the database connection is managed within the closure's scope, and your other modules interact with it through the dbManager object's methods, ensuring clean connection handling.


Also in this case we can Tracking Asynchronous Operations with Counters, something like this:


   1:  function createAsyncCounter(totalOperations, completionCallback) {
   2:      let completedOperations = 0;
   3:      return function operationComplete() {  // This inner function is the closure
   4:          completedOperations++;
   5:          if (completedOperations === totalOperations) {
   6:              completionCallback(); // Call the completion callback when all are done
   7:          }
   8:      };
   9:  }
  10:   
  11:  async function processMultipleInserts(inserts) {
  12:    const numInserts = inserts.length;
  13:    const onComplete = createAsyncCounter(numInserts, () => {
  14:      console.log('All insert operations complete!');
  15:      dbManager.disconnect(); // close when all inserts are done.
  16:    }); // Create a counter
  17:   
  18:    try {
  19:        for (const insert of inserts) {
  20:            dbManager.insert(insert.sql, insert.params, (rowCount, sql) => {
  21:              console.log(`Inserted ${rowCount} row(s). SQL: ${sql}`);
  22:              onComplete(); // increment and check the counter each insert is finished
  23:            })
  24:        }
  25:    } catch (error) {
  26:        console.error("Error during inserts:", error);
  27:        // Handle errors, potentially rollback changes if necessary
  28:    }
  29:  }
  30:   
  31:  const inserts = [
  32:      { sql: 'INSERT INTO my_table (col1, col2) VALUES ($1, $2)', params: ['val1', 'val2'] },
  33:      // ... more insert objects
  34:  ];
  35:  processMultipleInserts(inserts);

Sorry, I have no time, will continue soon.





FrontLearning context:



Comments ( )
Link to this page: http://www.vb-net.com/Closures/Index.htm
< THANKS ME>