node.jsnext.jsnode-sqlite3node-promisify

Am I using promisify() with bind() correctly?


I am writing middleware to return database queries using SQLite3 and Next.js. Here is a snippet of code that I wrote to return the contents of a DB table. It appears to work, and my checks of sources appear solid, with mixed reviews about the use of bind().

// route = /dbread
import { NextResponse } from 'next/server';
const sqlite3 = require("sqlite3").verbose();
const util = require('util');

export async function GET(req) {
    const db = new sqlite3.Database("./database.db");
    const dbAll = util.promisify(db.all).bind(db);
    
    let response = await dbAll('SELECT * FROM "text"')
        .then(db.close())
        .catch((err => {
            console.log(err);
            return ['error'];
            }));
    return NextResponse.json(JSON.stringify(response));
}

However, if I take bind out, it breaks, so I keep that in, even though I don't fully understand what it's doing. Is there a better way to do this? The solution seems simple enough, but I just want to make sure I'm stepping on solid ground.


Solution

  • What you have with .bind(db) works fine and is a correct way of doing things.

    I prefer to keep the same type of calling convention that other methods have by doing it like this:

    const db = new sqlite3.Database("./database.db");
    db.allP = util.promisify(db.all);
    

    And, then you call it like this:

    db.allP(...)
    

    like you would call any other method on the database, but that's just my personal preference. Either way will work. Calling this way also "binds" the db instance to the method call so it's properly available as this in the method for the method to use in its implementation.


    By way of explanation. When you call a method as in:

    obj.method(...)
    

    That makes the obj value available as this in the method's implementation. For your db implementation that is needed.

    When you do this:

    const dbAll = util.promisify(db.all);
    dbAll(...)
    

    dbAll() has become a regular function call and the value of this is either the global object (in non-strict mode) or undefined (if running in strict mode). And, thus the implementation of dbAll() does not have any access to the db variable which would make it not work. It expects db to be in this. Adding .bind() the way you did forces the value of this to be set to db. But, method calls as in obj.method() or db.allP() do this automatically for you. That's why db.allP() doesn't need .bind() to work properly.


    FYI, you may want to just use a sql module that has an interface that already supports promises rather than manually promisifying individual methods upon demand.