SQLite на Node.js с async/await

Интерфейс для синхронизации каждой функции SQLite и их использования с await.

Требуется модуль SQLite 3 для Node и Node.js 8.0, поддерживающий async/await.

SQLite чаще используется как средство хранения данных для локальных и мобильных приложений, также асинхронный стиль для чтения и записи в базу, и callbacks - не лучшее решение для доступа к данным в разных частях программы.

Чтобы получить более естественный доступ к данным в процедурной программе, я написал интерфейс, который преобразует callbacks в обещанные, чтобы можно было использовать каждую функцию с зарезервированным словом await .

Это не альтернатива аснихонному режиму, а скорее надстройка: можно использовать асинхронные и синхронные функции вместе.

Модуль aa-sql lite

Интерфейс в SQLite - это модуль с именем aa-sqlLite, его можно разместить в разделе node_modules приложения. Вот исходный код:

const sqlite3 = require('sqlite3').verbose()
var db

exports.db = db

exports.open=function(path) {
    return new Promise(function(resolve) {
    this.db = new sqlite3.Database(path, 
        function(err) {
            if(err) reject("Open error: "+ err.message)
            else    resolve(path + " opened")
        }
    )   
    })
}

// any query: insert/delete/update
exports.run=function(query) {
    return new Promise(function(resolve, reject) {
        this.db.run(query, 
            function(err)  {
                if(err) reject(err.message)
                else    resolve(true)
        })
    })    
}

// first row read
exports.get=function(query, params) {
    return new Promise(function(resolve, reject) {
        this.db.get(query, params, function(err, row)  {
            if(err) reject("Read error: " + err.message)
            else {
                resolve(row)
            }
        })
    }) 
}

// set of rows read
exports.all=function(query, params) {
    return new Promise(function(resolve, reject) {
        if(params == undefined) params=[]

        this.db.all(query, params, function(err, rows)  {
            if(err) reject("Read error: " + err.message)
            else {
                resolve(rows)
            }
        })
    }) 
}

// each row returned one by one 
exports.each=function(query, params, action) {
    return new Promise(function(resolve, reject) {
        var db = this.db
        db.serialize(function() {
            db.each(query, params, function(err, row)  {
                if(err) reject("Read error: " + err.message)
                else {
                    if(row) {
                        action(row)
                    }    
                }
            })
            db.get("", function(err, row)  {
                resolve(true)
            })            
        })
    }) 
}

exports.close=function() {
    return new Promise(function(resolve, reject) {
        this.db.close()
        resolve(true)
    }) 
}

Метод get возвращает строку из базы, а все возвращает массив строк.

В случае each это сложнее, чем SQLite вызывает функцию обратного вызова для каждой строки, отвечающей условию запроса. Решение заключается в том, чтобы использовать Database.serialize для обработки их один за другим, а затем вызвать окончательный пустой метод get для решения обещанной.

Показ

В этой демонстрации приводится пример использования каждой функции aa-sql. В первой части открываем базу, добавляем таблицу и заполняем ее парой строк. Потом база закрывается, ее снова открывают, чтобы выполнить несколько синхронных запросов.

const fs = require("fs")
const sqlite = require("aa-sqlite")

async function mainApp() {
    
    console.log(await sqlite.open('./users.db'))
    
    // Adds a table
    
    var r = await sqlite.run('CREATE TABLE users(ID integer NOT NULL PRIMARY KEY, name text, city text)')
    if(r) console.log("Table created")

    // Fills the table
    
    let users = {
        "Naomi": "chicago",
        "Julia": "Frisco",
        "Amy": "New York",
        "Scarlett": "Austin",
        "Amy": "Seattle"
    }
    
    var id = 1 
    for(var x in users) {
        var entry = `'${id}','${x}','${users[x]}'`
        var sql = "INSERT INTO users(ID, name, city) VALUES (" + entry + ")"
        r = await sqlite.run(sql)
        if(r) console.log("Inserted.")
        id++        
    }

    // Starting a new cycle to access the data

    await sqlite.close();
    await sqlite.open('./users.db') 

    console.log("Select one user:")
    
    var sql = "SELECT ID, name, city FROM users WHERE name='Naomi'"
    r = await sqlite.get(sql)
    console.log("Read:", r.ID, r.name, r.city)
    
    console.log("Get all users:")
    
    sql = "SELECT * FROM users"
    r = await sqlite.all(sql, [])
    r.forEach(function(row) {
        console.log("Read:", row.ID, row.name, row.city)    
    })
    
    console.log("Get some users:")
    
    sql = "SELECT * FROM users WHERE name=?"
    r = await sqlite.all(sql, ['Amy'])
    r.forEach(function(row) {
        console.log("Read:", row.ID, row.name, row.city)    
    })

    console.log("One by one:")
    
    sql = "SELECT * FROM users"
    r = await sqlite.each(sql, [], function(row) {
        console.log("Read:", row.ID, row.name, row.city)    
    })

    if(r) console.log("Done.")

    sqlite.close();
}

try {
    fs.unlinkSync("./users.db")
}
catch(e) {
}

mainApp()

Поскольку метод all возвращает массив, для обработки содержимого каждой строки используется forEach.

Ты можешь специально проверить в случае метода each, что каждая возвращаемая строка обрабатывается до того, как программа покажет «Done». Это было бы не так с оригинальными асинхронными методами.

Ты можешь загрузить архив, содержащий модуль aa-sql litet и демо.

Перед запуском демо еще нужно установить SQLite 3.