Objection.js: Allowing queries to return null instead of undefined

Created on 23 Sep 2017  路  8Comments  路  Source: Vincit/objection.js

A number of QueryBuilder methods return undefined when nothing matches the query result. For example, .first(), .findOne(), and .findById(). This mimics knex's behavior.

Because queries can return individual values, sometimes it may be important to distinguish between a null result and no result at all.

Any yet, perhaps because of my Java background, trafficking in undefineds just seems wrong. It seems like a variable should only be undefined if its value hasn't been determined yet or if something's wrong with the program.

An orNull() method on QueryBuilder should satisfy anti-undefined stooges like me. It would simply translate undefineds into nulls and not require my code to do so every time.

I've customized the QueryBuilder as a stopgap measure, but I'm guessing there's a more efficient solution to be had within objection.js proper:

class ExtendedModel extends Objection.Model
{
    static get QueryBuilder() {
        return ExtendedQueryBuilder;
    }
}

class ExtendedQueryBuilder extends Objection.QueryBuilder
{
    orNull() {
        return this.then((result) => {
            return (result === undefined ? null : result);
        });
    }
}

This allows my models to have such elegant code as:

    static getById(id) {
        return MyModel.query().findById(id).orNull();
    }

I'd offer a PR, but the operation abstraction requires some effort to comprehend.

Most helpful comment

I wasn't talking about parsers, but objection. Off course that can matter in other applications.

Performance matters, but optimizing wrong things like this is the root of all evil and wasted time (and again I mean in the context of web servers and using ORMs).

All 8 comments

With javascript its pretty normal to use undefined to represent no result (instead of for example exception). I don't see a reason why this should be in objection core lib. Maybe a plugin would work for you?

I agree with @elhigu. This is pretty easy to implement using existing features. Actually the only way I would improve your implementation is by using runAfter instead of then so that the query isn't executed when orNull is called:

class ExtendedQueryBuilder extends Objection.QueryBuilder
{
    orNull() {
        return this.runAfter(result => {
            return (result === undefined ? null : result);
        });
    }
}

Also I always use code like this

const person = await Person.query().findById(1);

if (!person) {
  res.status(404);
}

Instead of comparing against undefined or null. I just test if the value is falsy.

Thanks for that runAfter() tip! Also thanks for pointing me to plugins.

I'm using the code assertion library in my test suite, and it doesn't appear to have a falsey test, so I have to say what the result it. code is modeled on chai.

Just FYI, I ran a performance test and found === to be about 13% faster than truthy tests. That's not a big deal in today's age of scaling containers, and truthy tests probably aren't all that common in code anyway. I have a background in performance coding and so try to always be conscious of performance implications.

var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;

(function () {
    let u;
    let n = null;
    let o = { x: 1, y: 2 };

    suite.add('truthy test', function() {
        if (u) { 'a'; }
        if (n) { 'b'; }
        if (o) { 'c'; }
        if (!u) { 'd'; }
        if (!n) { 'e'; }
        if (!o) { 'f'; }
    })
    .add('null test', function() {
        if (u === null) { 'a'; }
        if (n === null) { 'b'; }
        if (o === null) { 'c'; }
        if (u !== null) { 'd'; }
        if (n !== null) { 'e'; }
        if (o !== null) { 'f'; }
    })
    .add('undefined test', function() {
        if (u === undefined) { 'a'; }
        if (n === undefined) { 'b'; }
        if (o === undefined) { 'c'; }
        if (u !== undefined) { 'd'; }
        if (n !== undefined) { 'e'; }
        if (o !== undefined) { 'f'; }
    })
    .on('cycle', function(event) {
        console.log(String(event.target));
    })
    .on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
    })
    .run({ 'async': true });
})();

Output:

truthy test x 75,037,199 ops/sec 卤0.89% (85 runs sampled)
null test x 84,800,477 ops/sec 卤1.23% (86 runs sampled)
undefined test x 85,430,173 ops/sec 卤1.10% (83 runs sampled)
Fastest is undefined test,null test

That performance difference makes absolutely zero difference in real world applications. I mean you can run 75 million of those in a second. The bottlenecks are somewhere else with a 158% certainty.

My background is parsers. It matters there. It depends on the application.

In node, the main thread hands blocking off to other threads. Main thread throughput matters. The faster each request is dispatched (there is no synchronous waiting), the more requests processed.

But I'm not saying that truthy tests are going to make a difference. I'm just defending that code performance does matter in node.

I wasn't talking about parsers, but objection. Off course that can matter in other applications.

Performance matters, but optimizing wrong things like this is the root of all evil and wasted time (and again I mean in the context of web servers and using ORMs).

Interestingly, Microsoft is telling its devs to only use undefined and not null in Typescript. I see arguments for either convention. knex and objection have chosen their convention.

Was this page helpful?
0 / 5 - 0 ratings