Some ways to improve the power of front-end code with javascript

The javascript column introduces some ways to improve the power of the front-end code.
Free recommendation: JavaScript (video)
has dealt with all kinds of weird bugs in the past development experience, and realized that code robustness (robustness) is an important indicator of improving work efficiency and quality of life. This article mainly organizes improving code robustness Some thoughts.

I have sorted out articles about code robustness before


How to write unit tests
of JavaScript in a serious way. How to log in the code This article will continue to explore some other methods to help improve the robustness of JavaScript code in addition to unit testing and logging.

Access objects more securely.
Don’t trust interface data.
Don’t trust the parameters passed by the front end, and don’t trust the data returned by the background.

For example, an api/xxx/list interface, in accordance with the document convention

{

    code: 0,

    msg: "",

    data: [     // ... 具体数据

    ],

};复制代码

The front-end code may be written as

const {code, msg, data} = await fetchList()

data.forEach(()=>{})复制代码

Because we assume that the data returned in the background is an array, we directly use data.forEach, if some exceptions are missed during joint debugging

It is expected that data will return [] an empty array when there is no data, but the background implementation does not return the data field.
Subsequent interface updates, data has changed from an array to a dictionary, and it is not synchronized with the front end in time.
These times, use data.forEach. Will report an error,

Uncaught TypeError: data.forEach is not a function

So in these places where the return value of the background interface is used directly, it is best to add type detection

Array.isArray(data) && data.forEach(()=>{})复制代码

In the same way, when the backend processes the front-end request parameters, it should also perform related type detection.

Null merge operator
Due to the dynamic nature of JavaScript, when we query an object property such as xyz, it is best to check whether x and y exist

let z = x && x.y && x.y.z复制代码

It is very troublesome to write this often, and it is much simpler to safely access object properties in dart

var z = a?.y?.z;复制代码

In ES2020, a draft of null merge operators is proposed, including ?? and ?. operators, which can achieve the same function of securely accessing object properties as dart. Now you can open the latest version of Chrome to test
Insert picture description here

Before that, we can encapsulate a method to safely obtain object properties

function getObjectValueByKeyStr(obj, key, defaultVal = undefined) {    if (!key) return defaultVal;    let namespace = key.toString().split(".");    let value,

        i = 0,

        len = namespace.length;    for (; i < len; i++) {

        value = obj[namespace[i]];        if (value === undefined || value === null) return defaultVal;

        obj = value;

    }    return value;

}var x = { y: { z: 100,},};var val = getObjectValueByKeyStr(x, "y.z");// var val = getObjectValueByKeyStr(x, "zz");console.log(val);复制代码

The front end inevitably has to deal with various browsers and various devices. A very important issue is compatibility, especially now that we are accustomed to using ES2015 features to develop code. Polyfills can help solve most of our problems. .

Remember the exception handling
reference:

JS error handling MDN
js builds a unified exception handling solution for ui. This series of articles is very well written.
Exception handling is the primary guarantee for code robustness. There are two aspects to exception handling.

Appropriate error handling can summarize the user experience, and gracefully prompt the user when the code goes wrong.
Encapsulate the error handling, which can reduce the amount of development and decouple the error handling from the code.
Error object.
A custom error object can be thrown through the throw statement.

// Create an object type UserExceptionfunction UserException (message){  // 包含message和name两个属性

  this.message=message;  this.name="UserException";

}// 覆盖默认[object Object]的toStringUserException.prototype.toString = function (){  return this.name + ': "' + this.message + '"';

}// 抛出自定义错误function f(){    try {        throw new UserException("Value too high");

    }catch(e){        if(e instanceof UserException){            console.log('catch UserException')            console.log(e)

        }else{            console.log('unknown error')            throw e

        }

    }finally{        // 可以做一些退出操作,如关闭文件、关闭loading等状态重置

        console.log('done')        return 1000 // 如果finally中return了值,那么会覆盖前面try或catch中的返回值或异常

    }

}

f()复制代码

Synchronous code
For synchronous code, you can use the chain of responsibility mode to encapsulate errors, that is, if the current function can handle the error, it will be handled in catch: if the corresponding error cannot be handled, the catch will be thrown to the upper level again

function a(){    throw 'error b'}// 当b能够处理异常时,则不再向上抛出function b(){    try{

        a()

    }catch(e){        if(e === 'error b'){            console.log('由b处理')

        }else {            throw e

        }

    }

}function main(){    try {

        b()

    }catch(e){        console.log('顶层catch')

    }

}复制代码

Asynchronous code
Because catch cannot get the exceptions thrown in asynchronous code, in order to realize the responsibility chain, the exception handling needs to be passed to the asynchronous task through the callback function.

function a(errorHandler) {    let error = new Error("error a");    if (errorHandler) {

        errorHandler(error);

    } else {        throw error;

    }

}function b(errorHandler) {    let handler = e => {        if (e === "error b") {            console.log("由b处理");

        } else {

            errorHandler(e);

        }

    };    setTimeout(() => {

        a(handler);

    });

}let globalHandler = e => {    console.log(e);

};

b(globalHandler);复制代码

The exception handling of Prmise
Promise only contains three states: pending, rejected and fulfilled

let promise2 = promise1.then(onFulfilled, onRejected)复制代码

The following are a few rules for promises to throw exceptions

function case1(){    // 如果promise1是rejected态的,但是onRejected返回了一个值(包括undifined),那么promise2还是fulfilled态的,这个过程相当于catch到异常,并将它处理掉,所以不需要向上抛出。

    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'

    })

 

    p1.then((res)=>{        return 1

    }, (e)=>{        console.log(e)        return 2

    }).then((a)=>{        // 如果注册了onReject,则不会影响后面Promise执行

        console.log(a) // 收到的是2

    })

}function case2(){    //  在promise1的onRejected中处理了p1的异常,但是又抛出了一个新异常,,那么promise2的onRejected会抛出这个异常

    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'

    })

    p1.then((res)=>{        return 1

    }, (e)=>{        console.log(e)        throw 'error in p1 onReject'

    }).then((a)=>{}, (e)=>{        // 如果p1的 onReject 抛出了异常

        console.log(e)

    })

}function case3(){    // 如果promise1是rejected态的,并且没有定义onRejected,则promise2也会是rejected态的。

    var p1 = new Promise((resolve, reject)=>{        throw 'p1 error'

    })

 

    p1.then((res)=>{        return 1

    }).then((a)=>{        console.log('not run:', a)

    }, (e)=>{        // 如果p1的 onReject 抛出了异常

        console.log('handle p2:', e)

    })

}function case4(){    // // 如果promise1是fulfilled态但是onFulfilled和onRejected出现了异常,promise2也会是rejected态的,并且会获得promise1的被拒绝原因或异常。

    var p1 = new Promise((resolve, reject)=>{

        resolve(1)

    })

    p1.then((res)=>{        console.log(res)        throw 'p1 onFull error'

    }).then(()=>{}, (e)=>{        console.log('handle p2:', e)        return 123

    })

}复制代码

Therefore, we can handle the error of the current promise in onRejected, if not, then throw it to the next promise

async
async/await is essentially the syntactic sugar of promise, so you can also use the similar capture mechanism of promise.catch

function sleep(cb, cb2 =()=>{},ms = 100) {

    cb2()    return new Promise((resolve, reject) => {        setTimeout(() => {            try {

                cb();

                resolve();

            }catch(e){

                reject(e)

            }

        }, ms);

    });

}// 通过promise.catch来捕获async function case1() {    await sleep(() => {        throw "sleep reject error";

    }).catch(e => {        console.log(e);

    });

}// 通过try...catch捕获async function case2() {    try {        await sleep(() => {            throw "sleep reject error";

        })

    } catch (e) {        console.log("catch:", e);

    }

}// 如果是未被reject抛出的错误,则无法被捕获async function case3() {    try {        await sleep(()=>{}, () => {            // 抛出一个未被promise reject的错误

            throw 'no reject error'

        }).catch((e)=>{            console.log('cannot catch:', e)

        })

    } catch (e) {        console.log("catch:", e);

    }

}复制代码

When a more stable third-party module
implements some relatively small functions, such as date formatting, we may not be accustomed to finding a mature library from npm, but handwriting a functional package by ourselves, due to insufficient development time or test cases , When encountering some unconsidered boundary conditions, BUG is prone to appear.

This is also the fact that some small modules often appear on npm, such as the package that determines whether it is an odd number: isOdd, and the weekly download volume is actually 600,000.
Insert picture description here

One very important reason for using some more mature libraries is that these libraries are often tested by a large number of test cases and the community, and they are definitely safer than our more handy tool codes.

An example of personal experience is: according to UA to judge the user's current access to the device, the normal idea is to match through the regular, at that time, I wrote a

export function getOSType() {  const ua = navigator.userAgent  const isWindowsPhone = /(?:Windows Phone)/.test(ua)  const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone  const isAndroid = /(?:Android)/.test(ua)  // 判断是否是平板

  const isTablet =    /(?:iPad|PlayBook)/.test(ua) ||

    (isAndroid && !/(?:Mobile)/.test(ua)) ||

    (/(?:Firefox)/.test(ua) && /(?:Tablet)/.test(ua))  // 是否是iphone

  const isIPhone = /(?:iPhone)/.test(ua) && !isTablet  // 是否是pc

  const isPc = !isIPhone && !isAndroid && !isSymbian && !isTablet  return {

    isIPhone,

    isAndroid,

    isSymbian,

    isTablet,

    isPc

  }

}复制代码

After going online, it was found that the logical judgment of some Xiaomi tablet users was abnormal, and the logbook saw that the UA was

"Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; MI PAD 4 Build/OPM1.171019.019) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/3.8.5.129 Mobile Safari/537.36复制代码

Even if the MI PAD is added to the regular judgment to temporarily fix it, what if the special UA of other devices appears later? Therefore, it is difficult to take all the problems into consideration when writing with my own experience, and replace it with the mobile-detect library later.

The disadvantage of using modules is

It may increase file dependency volume, increase packaging time, etc. This problem can be solved by packaging configuration, packaging third-party modules that do not change frequently into the vendor file. Configuration cache.
In some projects, third-party modules may need to be reduced due to security considerations. use, or be required to source code review
course during the module selection time must also be a variety of considerations, including stability, compatible with older versions, the unresolved issue and other issues. After choosing a better tool module, we can focus more on business logic.

Local configuration files
In the development environment, we may need some local switch configuration files. These configurations only exist during local development, do not enter the code base, and will not conflict with other colleagues' configurations.

I highly recommend hosting the mock template in the git repository, so that it is convenient for other colleagues to develop and debug the interface. When a problem is caused, a switch to import the mock file may be needed locally.

The following is a common practice: create a new local configuration file config.local.js, and then export the relevant configuration information

// config.local.jsmodule.exports = {  needMock: true}复制代码

Remember to ignore the file in .gitignore

config.local.js复制代码

Then load the module through try...catch..., because the file has not entered the code base, it will enter the catch process when the code is updated elsewhere, and the local development enters the normal module introduction process

// mock/entry.jstry {  const { needMock } = require('./config.local')  if (needMock) {    require('./index') // 对应的mock入口

    console.log('====start mock api===')

  }

} catch (e) {  console.log('未引入mock,如需要,请创建/mock/config.local并导出 {needMock: true}')

}复制代码

Finally, determine the development environment in the entry file of the entire application and import it

if (process.env.NODE_ENV === 'development') {  require('../mock/entry')

}复制代码

In this way, you can happily perform various configurations during local development without worrying about forgetting to comment the corresponding configuration changes before submitting the code~

Code Review
reference:

Code review is a bitter but interesting practice.
Code review should be a necessary step before going online. I think the main role of CR is

Able to confirm whether there is a deviation in the understanding of the requirements, and avoid wrangling

Optimize code quality, including redundant code, variable naming and over-encapsulation, etc. At least in addition to the code writer, it is necessary to ensure that the reviewer can understand the relevant logic

For a project that requires long-term maintenance and iteration, every commit and merge is very important, so before merging the code, it is best to check the changed code from scratch. Even if you are in a small team or cannot find reviewers, take the merger seriously.

Summary
This article mainly organizes some methods to improve the robustness of JavaScript code, mainly organizes

Safely access object properties to avoid code errors caused by data exceptions.
Catch exceptions and handle exceptions or report them through the chain of responsibility.
Use a more stable and safe third-party module,
take every merge seriously, and check the code before going online.
In addition, you need to To develop good programming habits, consider various boundary situations as much as possible.
This article comes from the javascript column of php Chinese website : https://www.php.cn/course/list/17.html

Guess you like

Origin blog.csdn.net/Anna_xuan/article/details/110237897