use more async/await 💥

This commit is contained in:
Jannis R 2021-12-29 18:53:50 +01:00
parent 40957d3515
commit b030eec1f5
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
19 changed files with 130 additions and 154 deletions

186
index.js
View file

@ -37,7 +37,7 @@ const createClient = (profile, userAgent, opt = {}) => {
throw new TypeError('userAgent must be a string'); throw new TypeError('userAgent must be a string');
} }
const _stationBoard = (station, type, parse, opt = {}) => { const _stationBoard = async (station, type, parse, opt = {}) => {
if (isObj(station)) station = profile.formatStation(station.id) if (isObj(station)) station = profile.formatStation(station.id)
else if ('string' === typeof station) station = profile.formatStation(station) else if ('string' === typeof station) station = profile.formatStation(station)
else throw new TypeError('station must be an object or a string.') else throw new TypeError('station must be an object or a string.')
@ -73,25 +73,23 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatStationBoardReq({profile, opt}, station, type) const req = profile.formatStationBoardReq({profile, opt}, station, type)
const {res, common} = await profile.request({profile, opt}, userAgent, req)
if (!Array.isArray(res.jnyL)) return []
const ctx = {profile, opt, common, res}
// todo [breaking]: return object with realtimeDataUpdatedAt // todo [breaking]: return object with realtimeDataUpdatedAt
return profile.request({profile, opt}, userAgent, req) return res.jnyL.map(res => parse(ctx, res))
.then(({res, common}) => { .sort((a, b) => new Date(a.when) - new Date(b.when)) // todo
if (!Array.isArray(res.jnyL)) return []
const ctx = {profile, opt, common, res}
return res.jnyL.map(res => parse(ctx, res))
.sort((a, b) => new Date(a.when) - new Date(b.when)) // todo
})
} }
const departures = (station, opt = {}) => { const departures = async (station, opt = {}) => {
return _stationBoard(station, 'DEP', profile.parseDeparture, opt) return await _stationBoard(station, 'DEP', profile.parseDeparture, opt)
} }
const arrivals = (station, opt = {}) => { const arrivals = async (station, opt = {}) => {
return _stationBoard(station, 'ARR', profile.parseArrival, opt) return await _stationBoard(station, 'ARR', profile.parseArrival, opt)
} }
const journeys = (from, to, opt = {}) => { const journeys = async (from, to, opt = {}) => {
from = profile.formatLocation(profile, from, 'from') from = profile.formatLocation(profile, from, 'from')
to = profile.formatLocation(profile, to, 'to') to = profile.formatLocation(profile, to, 'to')
@ -210,30 +208,28 @@ const createClient = (profile, userAgent, opt = {}) => {
if (opt.results !== null) query.numF = opt.results if (opt.results !== null) query.numF = opt.results
if (profile.journeysOutFrwd) query.outFrwd = outFrwd if (profile.journeysOutFrwd) query.outFrwd = outFrwd
return profile.request({profile, opt}, userAgent, { const {res, common} = await profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'}, cfg: {polyEnc: 'GPA'},
meth: 'TripSearch', meth: 'TripSearch',
req: profile.transformJourneysQuery({profile, opt}, query) req: profile.transformJourneysQuery({profile, opt}, query)
}) })
.then(({res, common}) => { if (!Array.isArray(res.outConL)) return []
if (!Array.isArray(res.outConL)) return [] // todo: outConGrpL
// todo: outConGrpL
const ctx = {profile, opt, common, res} const ctx = {profile, opt, common, res}
const journeys = res.outConL const journeys = res.outConL
.map(j => profile.parseJourney(ctx, j)) .map(j => profile.parseJourney(ctx, j))
return { return {
earlierRef: res.outCtxScrB, earlierRef: res.outCtxScrB,
laterRef: res.outCtxScrF, laterRef: res.outCtxScrF,
journeys, journeys,
// todo [breaking]: rename to realtimeDataUpdatedAt // todo [breaking]: rename to realtimeDataUpdatedAt
realtimeDataFrom: res.planrtTS ? parseInt(res.planrtTS) : null, realtimeDataFrom: res.planrtTS ? parseInt(res.planrtTS) : null,
} }
})
} }
const refreshJourney = (refreshToken, opt = {}) => { const refreshJourney = async (refreshToken, opt = {}) => {
if ('string' !== typeof refreshToken || !refreshToken) { if ('string' !== typeof refreshToken || !refreshToken) {
throw new TypeError('refreshToken must be a non-empty string.') throw new TypeError('refreshToken must be a non-empty string.')
} }
@ -250,23 +246,22 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken) const req = profile.formatRefreshJourneyReq({profile, opt}, refreshToken)
return profile.request({profile, opt}, userAgent, req) const {res, common} = await profile.request({profile, opt}, userAgent, req)
.then(({res, common}) => { if (!Array.isArray(res.outConL) || !res.outConL[0]) {
if (!Array.isArray(res.outConL) || !res.outConL[0]) { const err = new Error('invalid response')
const err = new Error('invalid response') // technically this is not a HAFAS error
// technically this is not a HAFAS error // todo: find a different flag with decent DX
// todo: find a different flag with decent DX err.isHafasError = true
err.isHafasError = true throw err
throw err }
}
const ctx = {profile, opt, common, res} const ctx = {profile, opt, common, res}
return {
// todo [breaking]: rename to realtimeDataUpdatedAt return {
realtimeDataFrom: res.planrtTS ? parseInt(res.planrtTS) : null, // todo [breaking]: rename to realtimeDataUpdatedAt
...profile.parseJourney(ctx, res.outConL[0]) realtimeDataFrom: res.planrtTS ? parseInt(res.planrtTS) : null,
} ...profile.parseJourney(ctx, res.outConL[0])
}) }
} }
// Although the DB Navigator app passes the *first* stopover of the trip // Although the DB Navigator app passes the *first* stopover of the trip
@ -392,7 +387,7 @@ const createClient = (profile, userAgent, opt = {}) => {
}) })
} }
const locations = (query, opt = {}) => { const locations = async (query, opt = {}) => {
if (!isNonEmptyString(query)) { if (!isNonEmptyString(query)) {
throw new TypeError('query must be a non-empty string.') throw new TypeError('query must be a non-empty string.')
} }
@ -409,16 +404,14 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatLocationsReq({profile, opt}, query) const req = profile.formatLocationsReq({profile, opt}, query)
return profile.request({profile, opt}, userAgent, req) const {res, common} = await profile.request({profile, opt}, userAgent, req)
.then(({res, common}) => { if (!res.match || !Array.isArray(res.match.locL)) return []
if (!res.match || !Array.isArray(res.match.locL)) return []
const ctx = {profile, opt, common, res} const ctx = {profile, opt, common, res}
return res.match.locL.map(loc => profile.parseLocation(ctx, loc)) return res.match.locL.map(loc => profile.parseLocation(ctx, loc))
})
} }
const stop = (stop, opt = {}) => { const stop = async (stop, opt = {}) => {
if ('object' === typeof stop) stop = profile.formatStation(stop.id) if ('object' === typeof stop) stop = profile.formatStation(stop.id)
else if ('string' === typeof stop) stop = profile.formatStation(stop) else if ('string' === typeof stop) stop = profile.formatStation(stop)
else throw new TypeError('stop must be an object or a string.') else throw new TypeError('stop must be an object or a string.')
@ -432,25 +425,23 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatStopReq({profile, opt}, stop) const req = profile.formatStopReq({profile, opt}, stop)
return profile.request({profile, opt}, userAgent, req) const {res, common} = await profile.request({profile, opt}, userAgent, req)
.then(({res, common}) => { if (!res || !Array.isArray(res.locL) || !res.locL[0]) {
if (!res || !Array.isArray(res.locL) || !res.locL[0]) { // todo: proper stack trace?
// todo: proper stack trace? // todo: DRY with lib/request.js
// todo: DRY with lib/request.js const err = new Error('response has no stop')
const err = new Error('response has no stop') // technically this is not a HAFAS error
// technically this is not a HAFAS error // todo: find a different flag with decent DX
// todo: find a different flag with decent DX err.isHafasError = true
err.isHafasError = true err.code = INVALID_REQUEST
err.code = INVALID_REQUEST throw err
throw err }
}
const ctx = {profile, opt, res, common} const ctx = {profile, opt, res, common}
return profile.parseLocation(ctx, res.locL[0]) return profile.parseLocation(ctx, res.locL[0])
})
} }
const nearby = (location, opt = {}) => { const nearby = async (location, opt = {}) => {
validateLocation(location, 'location') validateLocation(location, 'location')
opt = Object.assign({ opt = Object.assign({
@ -465,19 +456,18 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatNearbyReq({profile, opt}, location) const req = profile.formatNearbyReq({profile, opt}, location)
return profile.request({profile, opt}, userAgent, req) const {res, common} = await profile.request({profile, opt}, userAgent, req)
.then(({common, res}) => { if (!Array.isArray(res.locL)) return []
if (!Array.isArray(res.locL)) return []
// todo: parse `.dur` walking duration? // todo: parse `.dur` walking duration?
const ctx = {profile, opt, common, res} const ctx = {profile, opt, common, res}
const results = res.locL.map(loc => profile.parseNearby(ctx, loc)) const results = res.locL.map(loc => profile.parseNearby(ctx, loc))
return Number.isInteger(opt.results) ? results.slice(0, opt.results) : results return Number.isInteger(opt.results)
}) ? results.slice(0, opt.results)
: results
} }
const trip = (id, lineName, opt = {}) => { const trip = async (id, lineName, opt = {}) => {
// todo [breaking]: remove lineName param, not needed anymore
if (!isNonEmptyString(id)) { if (!isNonEmptyString(id)) {
throw new TypeError('id must be a non-empty string.') throw new TypeError('id must be a non-empty string.')
} }
@ -495,16 +485,15 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatTripReq({profile, opt}, id, lineName) const req = profile.formatTripReq({profile, opt}, id, lineName)
const {res, common} = await profile.request({profile, opt}, userAgent, req)
const ctx = {profile, opt, common, res}
// todo [breaking]: return object with realtimeDataUpdatedAt // todo [breaking]: return object with realtimeDataUpdatedAt
return profile.request({profile, opt}, userAgent, req) return profile.parseTrip(ctx, res.journey)
.then(({common, res}) => {
const ctx = {profile, opt, common, res}
return profile.parseTrip(ctx, res.journey)
})
} }
// todo [breaking]: rename to trips()? // todo [breaking]: rename to trips()?
const tripsByName = (lineNameOrFahrtNr = '*', opt = {}) => { const tripsByName = async (lineNameOrFahrtNr = '*', opt = {}) => {
if (!isNonEmptyString(lineNameOrFahrtNr)) { if (!isNonEmptyString(lineNameOrFahrtNr)) {
throw new TypeError('lineNameOrFahrtNr must be a non-empty string.') throw new TypeError('lineNameOrFahrtNr must be a non-empty string.')
} }
@ -572,20 +561,19 @@ const createClient = (profile, userAgent, opt = {}) => {
} }
req.jnyFltrL = [...req.jnyFltrL, ...opt.additionalFilters] req.jnyFltrL = [...req.jnyFltrL, ...opt.additionalFilters]
// todo [breaking]: return object with realtimeDataUpdatedAt const {res, common} = await profile.request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'}, cfg: {polyEnc: 'GPA'},
meth: 'JourneyMatch', meth: 'JourneyMatch',
req, req,
}) })
// todo [breaking]: catch `NO_MATCH` errors, return [] // todo [breaking]: catch `NO_MATCH` errors, return []
.then(({res, common}) => { const ctx = {profile, opt, common, res}
const ctx = {profile, opt, common, res}
return res.jnyL.map(t => profile.parseTrip(ctx, t)) // todo [breaking]: return object with realtimeDataUpdatedAt
}) return res.jnyL.map(t => profile.parseTrip(ctx, t))
} }
const radar = ({north, west, south, east}, opt) => { const radar = async ({north, west, south, east}, opt) => {
if ('number' !== typeof north) throw new TypeError('north must be a number.') if ('number' !== typeof north) throw new TypeError('north must be a number.')
if ('number' !== typeof west) throw new TypeError('west must be a number.') if ('number' !== typeof west) throw new TypeError('west must be a number.')
if ('number' !== typeof south) throw new TypeError('south must be a number.') if ('number' !== typeof south) throw new TypeError('south must be a number.')
@ -608,14 +596,12 @@ const createClient = (profile, userAgent, opt = {}) => {
const req = profile.formatRadarReq({profile, opt}, north, west, south, east) const req = profile.formatRadarReq({profile, opt}, north, west, south, east)
// todo [breaking]: return object with realtimeDataUpdatedAt const {res, common} = await profile.request({profile, opt}, userAgent, req)
return profile.request({profile, opt}, userAgent, req) if (!Array.isArray(res.jnyL)) return []
.then(({res, common}) => { const ctx = {profile, opt, common, res}
if (!Array.isArray(res.jnyL)) return []
const ctx = {profile, opt, common, res} // todo [breaking]: return object with realtimeDataUpdatedAt
return res.jnyL.map(m => profile.parseMovement(ctx, m)) return res.jnyL.map(m => profile.parseMovement(ctx, m))
})
} }
const reachableFrom = async (address, opt = {}) => { const reachableFrom = async (address, opt = {}) => {

View file

@ -114,8 +114,8 @@ tap.test('journeys  only subway', async (t) => {
t.end() t.end()
}) })
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: spichernstr, fromId: spichernstr,

View file

@ -92,8 +92,8 @@ tap.test('journeys  Ettelbruck to Luxembourg', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: ettelbruck, fromId: ettelbruck,

View file

@ -58,8 +58,8 @@ tap.test('journeys  Broadie Oaks to Domain', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: broadieOaks, fromId: broadieOaks,

View file

@ -104,8 +104,8 @@ tap.test('journeys  Berlin Schwedter Str. to München Hbf', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: blnSchwedterStr, fromId: blnSchwedterStr,

View file

@ -60,8 +60,8 @@ tap.skip('journeys  Hamburg Tiefstack to Hamburg Barmbek', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.skip('journeys  fails with no product', (t) => { tap.skip('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: tiefstack, fromId: tiefstack,

View file

@ -79,8 +79,8 @@ tap.test('journeys  Magdeburg Hbf to Magdeburg-Buckau', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: magdeburgHbf, fromId: magdeburgHbf,

View file

@ -78,8 +78,8 @@ tap.test('journeys  Ingolstadt Hbf to Audi Parkplatz', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: ingolstadtHbf, fromId: ingolstadtHbf,

View file

@ -21,33 +21,25 @@ const testEarlierLaterJourneys = async (cfg) => {
t.ok(model.laterRef) t.ok(model.laterRef)
// departure/arrival and earlierThan/laterThan should be mutually exclusive // departure/arrival and earlierThan/laterThan should be mutually exclusive
t.throws(() => { await t.rejects(async () => {
fetchJourneys(fromId, toId, { await fetchJourneys(fromId, toId, {
departure: when, earlierThan: model.earlierRef departure: when, earlierThan: model.earlierRef
}) })
// silence rejections, we're only interested in exceptions
.catch(() => {})
}) })
t.throws(() => { await t.rejects(async () => {
fetchJourneys(fromId, toId, { await fetchJourneys(fromId, toId, {
departure: when, laterThan: model.laterRef departure: when, laterThan: model.laterRef
}) })
// silence rejections, we're only interested in exceptions
.catch(() => {})
}) })
t.throws(() => { await t.rejects(async () => {
fetchJourneys(fromId, toId, { await fetchJourneys(fromId, toId, {
arrival: when, earlierThan: model.earlierRef arrival: when, earlierThan: model.earlierRef
}) })
// silence rejections, we're only interested in exceptions
.catch(() => {})
}) })
t.throws(() => { await t.rejects(async () => {
fetchJourneys(fromId, toId, { await fetchJourneys(fromId, toId, {
arrival: when, laterThan: model.laterRef arrival: when, laterThan: model.laterRef
}) })
// silence rejections, we're only interested in exceptions
.catch(() => {})
}) })
let earliestDep = Infinity, latestDep = -Infinity let earliestDep = Infinity, latestDep = -Infinity

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const journeysFailsWithNoProduct = (cfg) => { const journeysFailsWithNoProduct = async (cfg) => {
const { const {
test: t, test: t,
fetchJourneys, fetchJourneys,
@ -13,11 +13,9 @@ const journeysFailsWithNoProduct = (cfg) => {
const noProducts = Object.create(null) const noProducts = Object.create(null)
for (let p of products) noProducts[p.id] = false for (let p of products) noProducts[p.id] = false
t.throws(() => { await t.rejects(async () => {
fetchJourneys(fromId, toId, {departure: when, products: noProducts}) await fetchJourneys(fromId, toId, {departure: when, products: noProducts})
// silence rejections, we're only interested in exceptions })
.catch(() => {})
}, 'no products used')
} }
module.exports = journeysFailsWithNoProduct module.exports = journeysFailsWithNoProduct

View file

@ -96,8 +96,8 @@ tap.test('journeys  Ettelbruck to Luxembourg', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: ettelbruck, fromId: ettelbruck,

View file

@ -101,8 +101,8 @@ tap.test('journeys  Kiel Hbf to Flensburg', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: kielHbf, fromId: kielHbf,

View file

@ -60,8 +60,8 @@ tap.test('journeys  Kassel Scheidemannplatz to Kassel Auestadion', async (t)
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: scheidemannplatz, fromId: scheidemannplatz,

View file

@ -89,8 +89,8 @@ tap.test('journeys Salzburg Hbf to Wien Westbahnhof', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: salzburgHbf, fromId: salzburgHbf,

View file

@ -52,8 +52,8 @@ tap.test('journeys  Næstved to Aalborg', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: næstved, fromId: næstved,

View file

@ -69,8 +69,8 @@ const thomasMannStr = {
// @todo prices/tickets // @todo prices/tickets
// @todo journeys, only one product // @todo journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: saarbrueckenHbf, fromId: saarbrueckenHbf,

View file

@ -80,8 +80,8 @@ tap.test('journeys  Mittersendling to Karl-Theodor-Straße', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: mittersendling, fromId: mittersendling,

View file

@ -110,8 +110,8 @@ tap.test('journeys only subway', async (t) => {
// todo: journeys with arrival time // todo: journeys with arrival time
tap.test('journeys fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: spichernstr, fromId: spichernstr,

View file

@ -56,8 +56,8 @@ tap.test('journeys  Ludwigshafen to Meckesheim', async (t) => {
// todo: journeys, only one product // todo: journeys, only one product
tap.test('journeys  fails with no product', (t) => { tap.test('journeys fails with no product', async (t) => {
journeysFailsWithNoProduct({ await journeysFailsWithNoProduct({
test: t, test: t,
fetchJourneys: client.journeys, fetchJourneys: client.journeys,
fromId: ludwigshafen, fromId: ludwigshafen,