◆ この場合は try-catch してもほとんど変わりなさそう

try-catch は遅いということもあったのでコールバックの Promise 化+ await もしないほうがいいのかなと思いました

Node.js でよく見るものですが エラー時はコールバック関数の最初の引数にエラー情報が渡されます
最初の引数がないなら正常な結果を表します

ただコールバック関数はネストが深くなるとか見づらいとかの問題があって最近は Promise が一般的です
コールバック関数を受け取る関数を Promise を返す関数に変換するのは簡単で

function toPromise(fn){
return (...args) => new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if(err) reject(err)
else resolve(result)
})
})
}

これだけでできます
ただそれだけでも毎回書くのは面倒なので Node.js では util.promisify に↑みたいなことをして変換してくれる関数が用意されてます
関数固有の Promise 化処理に対応したり result が複数のに対応したりでこれよりはもう少し高機能だったはずです

Promise 化したあとは基本は async/await を使って扱うかと思います
となるとエラー時は try-catch の catch 句で処理されます
たまにエラーならいいのですが ものによっては基本がエラーになるようなケースがあります
例えばデータの存在確認にとりあえず read してみないといけない場合です
通常はなくて新規追加 ときどきすでにあるので更新処理の場合は基本が read できないので error 扱いです
こうなると async/await にして try-catch するとコールバックより遅い気がします

というわけで試してみました

const util = require("util")

function fn(callback) {
setTimeout(() => callback(1), 0)
}
const pfn = util.promisify(fn)

async function case1() {
try {
await pfn()
return true
} catch (err) {
return false
}
}

async function case2() {
return new Promise(resolve => fn(err => resolve(!err)))
}

!(async function() {
console.assert((await case1()) === (await case2()))

async function measure(num, name, fn) {
console.time(name)
for (let i = 0; i < num; i++) await fn()
console.timeEnd(name)
}

const m = measure.bind(null, 10000)
await m("1", case1)
await m("2", case2)
})()
1: 1348.750ms
2: 1304.362ms

10000 回の結果だとほぼ変わりません
わずかに await したほうが遅いです
先に実行したほうが早いということもあるので逆にしてみると 稀に await したほうが早くなることがありましたが 基本 await のほうが遅めでした

まぁこの場合は特に気にしなくても良さそうですね