如何處理JavaScript中的非預(yù)期數(shù)據(jù)-創(chuàng)新互聯(lián)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何處理 JavaScript 中的非預(yù)期數(shù)據(jù),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

主要從事網(wǎng)頁(yè)設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、響應(yīng)式網(wǎng)站設(shè)計(jì)、程序開發(fā)、微網(wǎng)站、小程序設(shè)計(jì)等,憑借多年來(lái)在互聯(lián)網(wǎng)的打拼,我們?cè)诨ヂ?lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)絡(luò)營(yíng)銷經(jīng)驗(yàn),集策劃、開發(fā)、設(shè)計(jì)、營(yíng)銷、管理等多方位專業(yè)化運(yùn)作于一體,具備承接不同規(guī)模與類型的建設(shè)項(xiàng)目的能力。

動(dòng)態(tài)類型語(yǔ)言的較大問題就是無(wú)法保證數(shù)據(jù)流總是正確的,因?yàn)槲覀儫o(wú)法“強(qiáng)行控制”一個(gè)參數(shù)或變量,比方說(shuō),讓它不為 null。當(dāng)我們面對(duì)這些情況時(shí)的標(biāo)準(zhǔn)做法是簡(jiǎn)單地做一個(gè)判斷:

function foo (mustExist) {

if (!mustExist) throw new Error('Parameter cannot be null')

return ...

}

這樣做的問題在于會(huì)污染我們的代碼,因?yàn)橐S處做判斷,并且實(shí)際上也無(wú)法保證每一位開發(fā)代碼的人都像這樣判斷;我們甚至都不知道這樣被傳進(jìn)來(lái)的一個(gè)參數(shù)是 undefined 還是 null ,這在不同團(tuán)隊(duì)負(fù)責(zé)前后端的情況下司空見慣,也是大概率的情況。

如何以更好的方式讓“非預(yù)期”數(shù)據(jù)造成的副作用最小化呢?作為一個(gè) 后端開發(fā)者 ,我想給出一些個(gè)人化的意見。

一. 一切的源點(diǎn)

數(shù)據(jù)有多種來(lái)源,最主要的當(dāng)然就是 用戶輸入 。但是,也存在其它有缺陷數(shù)據(jù)的來(lái)源,比如數(shù)據(jù)庫(kù)、函數(shù)返回值中的隱形空數(shù)據(jù)、外部 API 等。

我們稍后將展開討論以如何不同的方式對(duì)待每一種的情況,要知道畢竟沒什么靈丹妙藥。大多數(shù)這些非預(yù)期數(shù)據(jù)的起源都是人為失誤,當(dāng)語(yǔ)言解析到 null 或 undefined 時(shí),與之配套的邏輯卻沒準(zhǔn)備好處理它們。

二. 用戶輸入

在這種情況下,我們能做的不多,如果是用戶輸入的問題,我們通過(guò)稱為 補(bǔ)水(Hydration) 的方式處理它。換句話說(shuō),我們得拿到用戶發(fā)來(lái)的原始輸入,比如一個(gè) API 中的負(fù)載,并將其轉(zhuǎn)換為我們可以無(wú)錯(cuò)應(yīng)用的某些形式。

在后端,當(dāng)使用 Express 這樣的 web 服務(wù)器時(shí),我們可以通過(guò)標(biāo)準(zhǔn)的 JSON Schema (https://www.npmjs.com/package/ajv) 或是 Joi 這樣的工具對(duì)來(lái)自前端的用戶輸入執(zhí)行所有的操作。

關(guān)于我們能用 Express 和 AJV 對(duì)一個(gè)路由做什么的例子可能是下面這樣:

const Ajv = require('ajv')

const Express = require('express')

const bodyParser = require('body-parser')

const app = Express()

const ajv = new Ajv()

app.use(bodyParser.json())

app.get('/foo', (req, res) => {

const schema = {

type: 'object',

properties: {

name: { type: 'string' },

password: { type: 'string' },

email: { type: 'string', format: 'email' }

},

additionalProperties: false

required: ['name', 'password', 'email']

}

const valid = ajv.validate(schema, req.body)

if (!valid) return res.status(422).json(ajv.errors)

// ...

})

app.listen(3000)

可見我們對(duì)一個(gè)路由中請(qǐng)求的 body 做了校驗(yàn),默認(rèn)情況下 body 是個(gè)從 body-parser 包中通過(guò)負(fù)載接收到的對(duì)象,在本例中將其傳到一個(gè) JSON-Schema 實(shí)例中校驗(yàn),看看其中的某個(gè)屬性是否有不同的類型或格式。

重要:注意我們返回了一個(gè) HTTP 422 Unprocessable Entity 狀態(tài)碼,意味著“無(wú)法處理的實(shí)體”。許多人對(duì)待像這樣 body 或者 query 錯(cuò)誤的請(qǐng)求,使用了表示整體錯(cuò)誤的 400 Bad Request報(bào)錯(cuò);在這種情況中,請(qǐng)求本身并沒有錯(cuò),只是用戶發(fā)送的數(shù)據(jù)不符合預(yù)期而已。

默認(rèn)值的可選參數(shù)

我們之前做的校驗(yàn)的一個(gè)額外收獲是,我們開啟了一種可能性,那就是 如果一個(gè)可選域沒有被傳值,一個(gè)空值也能被傳遞進(jìn)我們的應(yīng)用 。例如,想象一個(gè)有 page 和 size 兩個(gè)參數(shù)作為查詢字符串的分頁(yè)路由,但二者都不是必須的;如果它們都沒收到的話,必須設(shè)定一個(gè)默認(rèn)值。

理想的話,我們的控制器里應(yīng)該有一個(gè)像這樣的函數(shù):

function searchSomething (filter, page = 1, size = 10) {

// ...

}

注意:正如之前我們返回的 422 一樣,對(duì)于分頁(yè)查詢,重要的是返回恰當(dāng)?shù)臓顟B(tài)碼,無(wú)論何時(shí)對(duì)于一個(gè)只在返回值中包含了部分?jǐn)?shù)據(jù)的請(qǐng)求,都應(yīng)該返回 HTTP 206 Partial Content ,也就是 “不完整的內(nèi)容”;當(dāng)用戶到達(dá)最后一頁(yè)且再?zèng)]有更多數(shù)據(jù)時(shí),才返回 200;如果用戶嘗試查詢超出了總范圍的頁(yè)數(shù),則返回一個(gè) 204 No Content 。

這將會(huì)解決我們接受兩個(gè) 空值 的案例,但這觸碰到了在 JavaScript 中通常非常引起爭(zhēng)論的一點(diǎn)。 對(duì)于可選參數(shù)的默認(rèn)值,只假設(shè)了 當(dāng)且僅當(dāng) 其為空的情況,而為 null 時(shí)就不靈了。 所以如果我們這樣操作:

function foo (a = 10) {

console.log(a)

}

foo(undefined) // 10

foo(20) // 20

foo(null) // null

因此,不能僅靠可選參數(shù)。對(duì)于這樣的情況我們有兩種處理方式:

前端控制器中的 if 語(yǔ)句,雖然看著有點(diǎn)啰嗦:

function searchSomething (filter, page = 1, size = 10) {

if (!page) page = 1

if (!size) size = 10

// ...

}

直接用 JSON-Schema 處理路由:

可以再次使用 AJV 或 @expresso/validator 來(lái)校驗(yàn)數(shù)據(jù):

app.get('/foo', (req, res) => {

const schema = {

type: 'object',

properties: {

page: { type: 'number', default: 1 },

size: { type: 'number', default: 10 },

},

additionalProperties: false

}

const valid = ajv.validate(schema, req.params)

if (!valid) return res.status(422).json(ajv.errors)

// ...

})

三. 應(yīng)對(duì) Null 和 Undefined

我個(gè)人對(duì)在 JavaScript 中用 null 還是 undefined 來(lái)表示空值這類爭(zhēng)論興趣不大。

現(xiàn)在我們知道了每種定義,而 JavaScript 在 2020 將新增了兩個(gè)實(shí)驗(yàn)性的特性(譯注:部分引自 MDN)。

空值合并運(yùn)算符 ??

空值合并運(yùn)算符 ?? 是一個(gè)邏輯運(yùn)算符。當(dāng)左側(cè)操作數(shù)為 null 或 undefined 時(shí),其返回右側(cè)的操作數(shù)。否則返回左側(cè)的操作數(shù)。

let myText = '';

let notFalsyText = myText || 'Hello world';

console.log(notFalsyText); // Hello world

let preservingFalsy = myText ?? 'Hi neighborhood';

console.log(preservingFalsy); // ''

可選鏈操作符 ?.

?. 運(yùn)算符功能類似于 . 運(yùn)算符,不同之處在于如果鏈條上的一個(gè)引用 null 或 undefined, .操作符會(huì)引起一個(gè)錯(cuò)誤,而 ?. 操作符則會(huì)按照短路計(jì)算的方式返回一個(gè) undefined。

const adventurer = {

name: 'Alice',

cat: {

name: 'Dinah'

}

};

const dogName = adventurer.dog?.name;

console.log(dogName);

// undefined

console.log(adventurer.someNonExistentMethod?.())

// undefined

結(jié)合 空值合并運(yùn)算符 ?? 使用:

let customer = {

name: "Carl",

details: { age: 82 }

};

let customerCity = customer?.city ?? "Unknown city";

console.log(customerCity); // Unknown city

這兩項(xiàng)新增特性將讓事情簡(jiǎn)單得多,因?yàn)槲覀兛梢园呀裹c(diǎn)集中在 null 和 undefined 上從而作出恰當(dāng)?shù)牟僮髁?用 ?? 而不是布爾值判斷 !obj 更易于處理很多錯(cuò)誤情況。

四. 隱性 null 函數(shù)

這個(gè)暗中作祟的問題更加復(fù)雜。一些函數(shù)會(huì)假設(shè)要處理的數(shù)據(jù)都是正確填充的,但有時(shí)并不能如意:

function foo (num) {

return 23*num

}

若 num 為 null ,則函數(shù)返回值會(huì)為 0 (譯注:如果操作值之一不是數(shù)值,則被隱式調(diào)用 Number() 進(jìn)行轉(zhuǎn)換),這不符合我們的期望。在這種情況下,我們能做的只有加上判斷??尚械呐袛嘈问接袃煞N,第一種可以簡(jiǎn)單地使用 if :

function foo (num) {

if (!num) throw new Error('Error')

return 23*num

}

第二種辦法是使用一個(gè)叫做 Either 的 Monad(譯注:Monad 是一種對(duì)函數(shù)計(jì)算過(guò)程的通用抽象機(jī)制,關(guān)鍵是統(tǒng)一形式和操作模式,相當(dāng)于是把值包裝在一個(gè) context 中。/tupian/20230522/65449477 )中。對(duì)于數(shù)據(jù)是不是 null 這種模棱兩可的問題,這可是個(gè)好辦法;因?yàn)?JavaScript 已經(jīng)有了一個(gè)支持雙動(dòng)作流的原生的函數(shù),即 Promise :

function exists (value) {

return x != null

? Promise.resolve(value)

: Promise.reject(`Invalid value: ${value}`)

}

async function foo (num) {

return exists(num).then(v => 23 * v)

}

通過(guò)這種方式就可以把來(lái)自 exists 中的 catch 方法委派到調(diào)用 foo 的函數(shù)中:

function init (n) {

foo(n)

.then(console.log)

.catch(console.error)

}

init(12) // 276

init(null) // Invalid value: null

五. 外部 API 和數(shù)據(jù)庫(kù)記錄

這也是相當(dāng)常見的情況,特別是當(dāng)系統(tǒng)是在先前創(chuàng)建和填充的數(shù)據(jù)庫(kù)之上開發(fā)的時(shí)候。例如,一個(gè)沿用之前成功產(chǎn)品數(shù)據(jù)庫(kù)的新產(chǎn)品、在不同系統(tǒng)間整合用戶等等。

這里的大問題不在于不知道數(shù)據(jù)庫(kù),實(shí)際上則是我們不知道在數(shù)據(jù)庫(kù)層面有什么已經(jīng)被完成了,我們沒法證明數(shù)據(jù)會(huì)不會(huì)是 null 或 undefined 。另一個(gè)問題是缺乏文檔,難以令人滿意的數(shù)據(jù)庫(kù)文檔化還是會(huì)帶來(lái)前面一個(gè)問題。

因?yàn)榉祷刂禂?shù)據(jù)量可能較大,這樣的情況能施展的空間也不大,除了不得不對(duì)個(gè)別數(shù)據(jù)作出判斷外,在對(duì)成組的數(shù)據(jù)進(jìn)行正式操作之前用 map 或 filter 進(jìn)行一遍過(guò)濾是個(gè)好的做法。

拋出 Errors

對(duì)于數(shù)據(jù)庫(kù)和外部 API 中的服務(wù)器代碼使用 斷言函數(shù)(Assertion Functions) 也是個(gè)好的實(shí)踐,基本上這些函數(shù)的做法就是如果數(shù)據(jù)存在就返回否則報(bào)錯(cuò)。這類函數(shù)的大多數(shù)常見情況,比方說(shuō)有一個(gè)根據(jù)一個(gè) id 搜索某種數(shù)據(jù)的 API:

async function findById (id) {

if (!id) throw new InvalidIDError(id)

const result = await entityRepository.findById(id)

if (!result) throw new EntityNotFoundError(id)

return result

}

實(shí)際應(yīng)用中,應(yīng)把 Entity 替換為符合情況的名字,如 UserNotFoundError。

該做法之所以好,是因?yàn)槲覀兛梢杂眠@樣一個(gè)函數(shù)找到的 user,可以被另外的函數(shù)用來(lái)檢索位于其它數(shù)據(jù)庫(kù)中的相關(guān)數(shù)據(jù),比如用戶的詳細(xì)資料;而當(dāng)我們調(diào)用后一個(gè)檢索函數(shù)時(shí),前置函數(shù) findUser 已經(jīng) 保證 了 user 的真實(shí)存在,因?yàn)槿绻鲥e(cuò)就會(huì)拋出錯(cuò)誤并可以據(jù)此直接在路由邏輯中找到問題。

async function findUserProfiles (userId) {

const user = await findUser(userId)

const profile = await profileRepository.findById(user.profileId)

if (!profile) throw new ProfileNotFoundError(user.profileId)

return profile

}

路由邏輯會(huì)像這樣:

app.get('/users/{id}/profiles', handler)

// --- //

async function handler (req, res) {

try {

const userId = req.params.id

const profile = await userService.findUserProfiles(userId)

return res.status(200).json(profile)

} catch (e) {

if (e instanceof UserNotFoundError

|| e instanceof ProfileNotFoundError)

return res.status(404).json(e.message)

if (e instanceof InvalidIDError)

return res.status(400).json(e.message)

}

}

只要檢查錯(cuò)誤實(shí)例的名稱,就能得知返回了什么類型的錯(cuò)誤了。


在必要的地方單獨(dú)判斷非預(yù)期數(shù)據(jù);設(shè)置可選參數(shù)的默認(rèn)值;用 ajv 等工具對(duì)可能不完整的數(shù)據(jù)進(jìn)行補(bǔ)水處理;恰當(dāng)使用實(shí)驗(yàn)性的 空值合并運(yùn)算符 ?? 和 可選鏈操作符 ?.;用 Promise 包裝隱性的空值、統(tǒng)一操作模式;用前置的 map 或 filter 過(guò)濾成組數(shù)據(jù)中的非預(yù)期數(shù)據(jù);在職責(zé)明確的控制器函數(shù)中,各自拋出類型明確的錯(cuò)誤;用這些方法處理數(shù)據(jù)就能得到連續(xù)而可預(yù)測(cè)的信息流了。

上述就是小編為大家分享的如何處理 JavaScript 中的非預(yù)期數(shù)據(jù)了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

本文標(biāo)題:如何處理JavaScript中的非預(yù)期數(shù)據(jù)-創(chuàng)新互聯(lián)
轉(zhuǎn)載來(lái)源:http://bm7419.com/article46/gochg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、虛擬主機(jī)標(biāo)簽優(yōu)化、云服務(wù)器網(wǎng)站建設(shè)、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司