Skip to content

Commit

Permalink
Align nextTick behavior with abstract-leveldown
Browse files Browse the repository at this point in the history
By using `queueMicrotask()` in browsers, for a consistent experience
regardless of bundler (and of runtime in the future, when we drop
node 10). Webpack no longer shims node core modules.

In theory bundlers can now skip shimming `process`, were it not for
our `readable-stream` dependency which uses `process.nextTick()` at
the time of writing and has more reason to keep doing so. In modules
where the risk of timing issues (due to not having a common microtask
scheduler between modules) is low, like here in `levelup`, I prefer
the idiomatic `queueMicroTask()` function.

Lastly, maybe streams are not here to stay. Async iterators are
coming to the level ecosystem. Or maybe we'll switch to `streamx`
which is planning to use `queueMicrotask()` as well.
  • Loading branch information
vweevers committed Apr 17, 2021
1 parent 29d8b5d commit 4b35716
Show file tree
Hide file tree
Showing 10 changed files with 39 additions and 13 deletions.
11 changes: 7 additions & 4 deletions lib/levelup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const catering = require('catering')
const getCallback = require('./common').getCallback
const getOptions = require('./common').getOptions

// TODO: after we drop node 10, also use queueMicrotask() in node
const nextTick = require('./next-tick')

const WriteError = errors.WriteError
const ReadError = errors.ReadError
const NotFoundError = errors.NotFoundError
Expand Down Expand Up @@ -46,7 +49,7 @@ function LevelUP (db, options, callback) {
if (!db || typeof db !== 'object') {
error = new InitializationError('First argument must be an abstract-leveldown compliant store')
if (typeof callback === 'function') {
return process.nextTick(callback, error)
return nextTick(callback, error)
}
throw error
}
Expand Down Expand Up @@ -97,7 +100,7 @@ LevelUP.prototype.open = function (opts, callback) {
}

if (this.isOpen()) {
process.nextTick(callback, null, this)
nextTick(callback, null, this)
return callback.promise
}

Expand Down Expand Up @@ -132,7 +135,7 @@ LevelUP.prototype.close = function (callback) {
this.emit('closing')
this.db = new DeferredLevelDOWN(this._db)
} else if (this.isClosed()) {
process.nextTick(callback)
nextTick(callback)
} else if (this.db.status === 'closing') {
this.once('closed', callback)
} else if (this._isOpening()) {
Expand Down Expand Up @@ -299,7 +302,7 @@ LevelUP.prototype.type = 'levelup'

function maybeError (db, callback) {
if (!db._isOpening() && !db.isOpen()) {
process.nextTick(callback, new ReadError('Database is not open'))
nextTick(callback, new ReadError('Database is not open'))
return true
}
}
Expand Down
11 changes: 11 additions & 0 deletions lib/next-tick-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict'

const queueMicrotask = require('queue-microtask')

module.exports = function (fn, ...args) {
if (args.length === 0) {
queueMicrotask(fn)
} else {
queueMicrotask(() => fn(...args))
}
}
3 changes: 3 additions & 0 deletions lib/next-tick.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict'

module.exports = process.nextTick
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
"description": "Fast & simple storage - a Node.js-style LevelDB wrapper",
"license": "MIT",
"main": "lib/levelup.js",
"browser": {
"./lib/next-tick.js": "./lib/next-tick-browser.js"
},
"scripts": {
"test": "standard && hallmark && (nyc -s node test/self.js | faucet) && nyc report",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"test-browsers": "airtap --verbose test/self.js > airtap.log",
"test-browsers-local": "airtap -p local test/self.js",
"hallmark": "hallmark --fix",
"dependency-check": "dependency-check --no-dev .",
"dependency-check": "dependency-check --no-dev -i queue-microtask .",
"prepublishOnly": "npm run dependency-check"
},
"files": [
Expand All @@ -25,7 +28,8 @@
"deferred-leveldown": "~5.3.0",
"level-errors": "^3.0.0",
"level-iterator-stream": "^5.0.0",
"level-supports": "^2.0.0"
"level-supports": "^2.0.0",
"queue-microtask": "^1.2.3"
},
"devDependencies": {
"after": "^0.8.2",
Expand Down
3 changes: 2 additions & 1 deletion test/create-stream-vs-put-racecondition.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const levelup = require('../lib/levelup')
const nextTick = require('../lib/next-tick')
const memdown = require('memdown')
const encdown = require('encoding-down')
const after = require('after')
Expand All @@ -23,7 +24,7 @@ function makeTest (test, encode, deferredOpen, delayedPut) {

test(name, function (t) {
const db = encode ? levelup(encdown(memdown())) : levelup(memdown())
const delay = delayedPut ? process.nextTick : callFn
const delay = delayedPut ? nextTick : callFn

run(t, db, !deferredOpen, delay)
})
Expand Down
3 changes: 2 additions & 1 deletion test/idempotent-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const levelup = require('../lib/levelup.js')
const nextTick = require('../lib/next-tick')
const memdown = require('memdown')
const sinon = require('sinon')

Expand All @@ -18,7 +19,7 @@ module.exports = function (test, testCommon) {

// close needs to be idempotent too.
db.close()
process.nextTick(db.close.bind(db))
nextTick(db.close.bind(db))
}

const db = levelup(memdown(), function () {
Expand Down
5 changes: 3 additions & 2 deletions test/init-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const levelup = require('../lib/levelup')
const nextTick = require('../lib/next-tick')
const memdown = require('memdown')

module.exports = function (test, testCommon) {
Expand Down Expand Up @@ -51,7 +52,7 @@ module.exports = function (test, testCommon) {

const mem = memdown()
mem._open = function (opts, cb) {
process.nextTick(cb, new Error('from underlying store'))
nextTick(cb, new Error('from underlying store'))
}

levelup(mem, function (err) {
Expand All @@ -68,7 +69,7 @@ module.exports = function (test, testCommon) {

const mem = memdown()
mem._open = function (opts, cb) {
process.nextTick(cb, new Error('from underlying store'))
nextTick(cb, new Error('from underlying store'))
}

levelup(mem)
Expand Down
3 changes: 2 additions & 1 deletion test/read-stream-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const bigBlob = Array.apply(null, Array(1024 * 100)).map(function () { return 'a
const discardable = require('./util/discardable')
const readStreamContext = require('./util/rs-context')
const rsFactory = require('./util/rs-factory')
const nextTick = require('../lib/next-tick')

module.exports = function (test, testCommon) {
const createReadStream = rsFactory(testCommon)
Expand Down Expand Up @@ -523,7 +524,7 @@ module.exports = function (test, testCommon) {
const rs = createReadStream(db)
.on('close', done)

process.nextTick(function () {
nextTick(function () {
rs.destroy()
})
})
Expand Down
2 changes: 1 addition & 1 deletion test/self.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ suite({
// Integration tests that can't use a generic testCommon.factory()
require('./self/manifest-test')

if (!process.browser) {
if (typeof process === 'undefined' || !process.browser) {
require('./browserify-test')(test)
}
3 changes: 2 additions & 1 deletion test/snapshot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const trickle = require('trickle')
const discardable = require('./util/discardable')
const readStreamContext = require('./util/rs-context')
const rsFactory = require('./util/rs-factory')
const nextTick = require('../lib/next-tick')

module.exports = function (test, testCommon) {
const createReadStream = rsFactory(testCommon)
Expand All @@ -28,7 +29,7 @@ module.exports = function (test, testCommon) {
done()
}, 0.05))

process.nextTick(function () {
nextTick(function () {
// 3) Concoct and write new random data over the top of existing items.
// If we're not using a snapshot then then we'd expect the test
// to fail because it'll pick up these new values rather than the
Expand Down

0 comments on commit 4b35716

Please sign in to comment.