aaronpowell / db.js

db.js is a wrapper for IndexedDB to make it easier to work against
http://aaronpowell.github.com/db.js/
MIT License
818 stars 142 forks source link

"undefined is not a function (evaluating 'a.addEventListener(b,c)')","name":"TypeError"} #187

Open ls-simon-he opened 6 years ago

ls-simon-he commented 6 years ago

Issue:

Got an exception when tried to add listener to db. "undefined is not a function (evaluating 'a.addEventListener(b,c)')","name":"TypeError"}

How to reproduce on Safari of iOS 8/9:

var server;
db.open({
    server: 'my-app',
    version: 1,
    schema: {
        people: {
            key: {keyPath: 'id', autoIncrement: true},
            // Optionally add indexes
            indexes: {
                firstName: {},
                answer: {unique: true}
            }
        }
    }
}).then(function (s) {
    server = s;
   // The line will throw error
    server.addEventListener('versionchange', function(e) {
    });
});
aaronpowell commented 6 years ago

I didn't think indexedDB was really classed as supported until iOS 10. According to caniuse.com indexeddb on 8/9 was marked as very unstable.

That said, looking at your sample code above the type of s in the resolved promise isn't an IndexedDB type, it's the Server type used internally by db.js, which doesn't expose that function, hence the error you're receiving.

Instead you need to call the getIndexedDB function to return the raw IndexedDB object.

That said, your code wouldn't work anyway as the promise will be resolved after the versionchange event has fired, it's captured here: https://github.com/aaronpowell/db.js/blob/master/src/db.js#L780 and I don't think there's anywhere I expose a hook into that event.

ls-simon-he commented 6 years ago

@aaronmccall Thanks for your clarifying. By tracing the code, I found the server.addEventListener() actually just forwards the event binding to the raw IndexedDB object at: https://github.com/aaronpowell/db.js/blob/master/src/db.js#L620 And just the line caused the exception: the raw IndexedDB doesn't support the method addEventListener()! Then I changed to db['on' + eventName] = handler;, it works. So here is the fix:

        this.addEventListener = function (eventName, handler) {
            if (!serverEvents.includes(eventName)) {
                throw new Error('Unrecognized event type ' + eventName);
            }
           const errorHandler =  function (e) {
                    e.preventDefault(); // Needed by Firefox to prevent hard abort with ConstraintError
                    handler(e);
            };
            const h = eventName === 'error' ? errorHandler : handler;
             if (db.addEventListner) {
                 db.addEventListener(eventName, h);
             } else {
                 db['on' + eventName] = h;
             }
        };

        this.removeEventListener = function (eventName, handler) {
            if (!serverEvents.includes(eventName)) {
                throw new Error('Unrecognized event type ' + eventName);
            }
            if (db.removeEventListener) {
                 db.removeEventListener(eventName, handler);
             } else {
                 db['on' + eventName] = null;
             }
        };
aaronpowell commented 6 years ago

Oh, I didn't know I exposed that functionality. Well there you go ey!

TBH I'm hesitant to add this change without understanding the underlying cause properly. I'd be quite shocked that a browser didn't support addEventListener on the indexedDB object needs to inherit from EventTarget, which all implementations did.

Also using the on<eventName> approach is not that great, you can only assign a single event listener to that event.