0%

Promise被reject时,then方法会执行吗?

promise.protyto.then什么时候会执行?我的印象里只有promise状态为fullfilled时才会执行,其实是不对的,promise状态为reject时,同样可以执行then方法。

先来看一个最常规的应用,我们在写代码时都是thencatch配合使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success in fetchData');
}, 100);
});
};

fetchData().then((data) => {
console.log(data);
}).catch((error) => {
console.log(error);
});

我们在fetchData中使用setTimeout模拟了一个异步操作,100ms后resolve,这时then中的回调函数会被执行,打印出success in fetchData

同理,假设100ms后reject这个promise,那么会打印出: error in fetchData

1
2
3
4
5
6
7
8
9
10
11
12
13
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error in fetchData');
}, 100);
});
};

fetchData().then((data) => {
console.log(data);
}).catch((error) => {
console.log(error);
});

在常规使用中,我们只给then传了一个方法作为参数,但是then其实可以接受两个参数的。如果我们传递了第二个参数,那么当promise状态为reject时,第二个参数对应的方法会被执行。

1
2
then(onFulfilled)
then(onFulfilled, onRejected)

所以,修改一下上面的代码,我们可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error in fetchData');
}, 100);
});
};

fetchData().then((data) => {
console.log(data);
}, (error) => {
console.log(error);
});

这样,当promise状态为reject时,第二个参数对应的方法会被执行,打印出error in fetchData。这和使用catch的效果是一样的。

那么如果我们既传递了then中的第二个参数onRejected又使用了catch,结果会怎样呢?我们来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error in fetchData');
}, 100);
});
};

fetchData().then((data) => {
console.log(data);
}, (error) => {
console.log(error);
}).catch((error) => {
console.log('catch:', error);
});

此时打印出的结果是error in fetchData,因为then中的第二个参数会优先被执行,catch不会被执行。

Promise.then中抛出的异常会被catch捕获吗?

Promise.then中抛出的异常会被catch捕获吗?我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success in fetchData');
}, 100);
});
};

fetchData().then((data) => {
console.log(data);
throw new Error('error in then');
}).catch((error) => {
console.log(error);
});

then中,我们抛出了一个异常,这个异常会被catch捕获吗?答案是会的,打印出Error: error in then

javascript destruction

1. Object destruction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const response = {
code: 200,
status: 'success',
data: {
name: 'zdd',
age: 18
}
}

const { code, status, data: { name, age } } = response;
console.log(code); // 200
console.log(status); // "success"
console.log(name); // "zdd"
console.log(age); // 18

2. String destruction

1
2
3
const [a, b, c, d, e] = 'hello';
console.log(a); // "h"
console.log(b); // "e"

3. rename on destruction

1
2
3
4
5
6
const person = {
name: 'zdd',
age: 18
}
const { name: myName } = person;
console.log(myName); // "zdd"

4. default value on destruction

1
2
3
4
5
6
7
8
9
const person = {
name: 'zdd',
age: 18,
};

const { name, age = 20, gender = 'male' } = person;
console.log(name); // 'zdd'
console.log(age); // 18
console.log(gender); // 'male'

Note: Each destructed property can have a default value if and only if the property is not present or its value is undefined.

1
2
3
4
5
6
7
8
9
10
const person = {
name: 'zdd',
age: 18,
address: null
};

const { name, address = 'Dalian', gender = 'male' } = person;
console.log(name); // zdd
console.log(address); // null
console.log(gender); // male

In the above code, address is present in the person object, but its value is null, so the default value Dalian is not used.

如果解构中的属性值为null,则无法进行嵌套解构,会报错。

1
2
3
4
5
6
7
8
9
const person = {
name: 'zdd',
address: null
};

const { name, address: { city = 'Dalian', street = 'Jiefang Road' } = {} } = person;
console.log(name); // zdd
console.log(city); // Cannot read properties of null (reading 'city')
console.log(street); // Cannot read properties of null (reading 'street')

注意,以上代码会报错,虽然我们给了address一个默认值{},但是address的原始值是null,所以默认值无法生效,这导致了citystreet的解构报错。(相当于在null值中读取citystreet

要想解决这个问题,只能将嵌套解构变为二次解构。

1
2
3
4
5
6
7
8
9
10
const person = {
name: 'zdd',
address: null
};

const { name, address = {} } = person;
const { city = 'Dalian', street = 'Jiefang Road' } = address || {};
console.log(name); // zdd
console.log(city); // Dalian
console.log(street); // Jiefang Road

第一次解构后,address的值为null,所以我们给address一个默认值{},然后再次解构citystreet,这样就不会报错了。

5. rename and default value at the same time

1
2
3
4
5
6
7
8
9
const person = {
name: 'zdd',
age: 18,
};

const { name: myName, age: myAge = 20, gender: myGender = 'male'} = person;
console.log(myName); // 'zdd'
console.log(myAge); // 18
console.log(myGender); // 'male'

6. Array destruction

1
2
3
4
5
const nums = [1, 2, 3];
const [a, b, c] = nums;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

数组解构与对象解构不同,不需要指定属性名,只需要按照顺序解构即可。

1
2
3
4
const colors = ['red', 'green', 'blue'];
const [firstColor, secondColor] = colors;
console.log(firstColor); // 'red'
console.log(secondColor); // 'green'

7. get element at specific position

The following code get the color at the third position, ignore the first two colors.

1
2
3
const colors = ['red', 'green', 'blue'];
const [, , thirdColor] = colors;
console.log(thirdColor); // 'blue'

8. ignore some elements

Get all elements in array except the first two.

1
2
3
const colors = ['red', 'green', 'blue', 'yellow'];
const [, , ...restColors] = colors;
console.log(restColors); // ['blue', 'yellow']

注意:数组解构中,不定元素必须放在最后,如果不定元素后面还有其他元素,会报错。

1
2
const colors = ['red', 'green', 'blue', 'yellow'];
const [...restColors, lastColor] = colors; // SyntaxError: Rest element must be last element

9. Copy array

ES5

1
2
3
const colors = ['red', 'green', 'blue'];
const newColors = colors.slice(); // or colors.concat();
console.log(newColors); // ['red', 'green', 'blue']

ES6

1
2
3
const colors = ['red', 'green', 'blue'];
const [...newColors] = colors;
console.log(newColors); // ['red', 'green', 'blue']

9. swap two variables

Before ES6, we must introduce a temporary variable to swap two variables. But with destruction, we can swap two variables easily.

1
2
3
4
5
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1

10. Nested array destruction

1
2
3
4
5
6
const nums = [1, [2, 3], 4];
const [a, [b, c], d] = nums;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // 4

JavaScript Promise Reject

See the following code, what’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function handleResponse(response) {
return new Promise((resolve, reject) => {
if (response.code !== 200) {
console.log('rejected!');
reject(response.code);
}

const {
data: { token },
} = response;
if (token) {
console.log('resolved!');
resolve(token);
}
});
}

const response = {
code: 201,
data: {
token: '123455',
},
};

handleResponse(response)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

The output is:

1
2
3
rejected!
resolved!
201

Why?

reject or resolve will not terminate the execution of the promise, it will continue to execute subsequent code.

To solve this problem, you can add a return statement after reject(response.code).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function handleResponse(response) {
return new Promise((resolve, reject) => {
if (response.code !== 200) {
console.log('rejected!');
return reject(response.code); // return reject here.
// return; // or return here.
}

const {
data: { token },
} = response;
if (token) {
console.log('resolved!');
resolve(token);
}
});
}

Or use the else statement to make the exclusive execution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function handleResponse(response) {
return new Promise((resolve, reject) => {
if (response.code !== 200) {
console.log('rejected!');
reject(response.code);
} else {
const {
data: { token },
} = response;
if (token) {
console.log('resolved!');
resolve(token);
}
}
});
}

Then we got the correct output as:

1
2
rejected!
201

conclusion

  • reject or returnwill not terminate the execution of the promise, it will continue to execute subsequent code.

Three ways to solve this problem:

  1. return resolved(xxx) or return reject(xxx), the return value will be ignored, so we can save a line then way 2.
  2. Add a return statement after reject(response.code).
  3. Use the if/else statement to make the exclusive execution.

webstorm-tips

Conflict between prettier and eslint

[ESlint] delete cr (prettier/prettier)

Resolution: put the following config in your .eslintrc.js under project root.

1
2
3
4
5
6
7
8
9
rules: {
...
'prettier/prettier': [
'error',
{
'endOfLine': 'auto' // This line will remove the warning
}
]
},

nest.js - geting start

Installation

1
npm i -g @nestjs/cli

Create a new project

1
nest new project-name

Create a new module

1
nest g module module-name

Create a new controller

1
nest g controller controller-name

Create a new service

1
nest g service service-name

Create a new pipe

1
nest g pipe pipe-name

Create a new resource

1
nest g resource resource-name

Database

Nest.js is database agnostic, you can use any database you want. For example, you can use TypeORM, Sequelize, Mongoose, etc.

Install TypeORM with mysql driver

1
npm install --save @nestjs/typeorm typeorm mysql2

Troubleshooting

Error: Nest can’t resolve dependencies of the SearchHistoryService (?). Please make sure that the argument “SearchHistoryRepository” at index [0] is available in the SearchHistoryModule context.

  1. 如果这个错误发生在一个module中(即不存在Module之间相互引用的情况),那么很可能是Module文件中缺少imports。加上下面这句:
1
2
3
4
5
6
7
8
9
10
11
12
import { Module } from '@nestjs/common';
import { SearchHistoryService } from './search_history.service';
import { SearchHistoryController } from './search_history.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SearchHistory } from './entities/search_history.entity';

@Module({
imports: [TypeOrmModule.forFeature([SearchHistory])], // 这一句不能少!!!这个问题困扰了我一整天!
controllers: [SearchHistoryController],
providers: [SearchHistoryService],
})
export class SearchHistoryModule {}
  1. 如果这个错误发生在module交叉引用中,比如A module中的service引用了B module的service,那么需
    要:
  • 在B module中exports需要被引用的service
  • 在A module中imports B module
1
2
3
4
5
// A module
imports: [BModule],

// B module
exports: [BService],

Database data was deleted after start nest.js

将app.module.ts中的synchronize设置为false,这样每次启动都不会删除数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'xxx',
host: 'xxx',
port: 1234,
username: 'xxx',
password: 'xxx',
database: 'xxx',
entities: [xxx],
logging: true,
synchronize: false, // 这里设置为false!!!切记
}),
XXXModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

TypeError: Cannot read property ‘setState’ of undefined

相信很多初学React的同学都差不多遇到过这个错误。

在js中,function中的this代表调用这个函数的object,也就是谁调用这个函数,那么this就指向谁,这个object可以是window,可以使document,也可以是button。

这个特性导致了React中一个常见的的找不到this的问题,且看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, {Component} from "react";

class ThisTest extends Component {
constructor(props) {
super(props);
this.state = {
name: 'zdd',
}
}

handleClick() {
this.setState({
name: 'ddz',
})
}

render() {
return (
<button onClick={this.handleClick}>hello</button>
)
}
}

export default ThisTest;

以上代码运行的时候会出现如下错误:

TypeError: Cannot read property 'setState' of undefined

为啥呢?

我们来分析一下,函数handleClick中有一个this,而ES6 Class中的方法默认不绑定this,所以出错了。怎么解决?两个办法:

方法一:将handleClick改为箭头函数,因为箭头函数中的this指向该函数所在的组件,如下:

1
2
3
4
5
handleClick = () => {
this.setState({
name: 'ddz',
})
};

方法二:用bind函数将调用的函数绑定到组件上,一般我们在constructor中做这个绑定,上面的代码可以变为:

1
2
3
4
5
6
7
8
9
10
11
class ThisTest extends Component {
constructor(props) {
super(props);
this.state = {
name: 'zdd',
};

// 添加下面一行。
this.handleClick = this.handleClick.bind(this);
}
}

当然网上还有其他方法,比如使用React.createClass来创建组件,这样会自动将this绑定到组件上,但这种创建组件的方法已经不推荐使用了,或者在render函数中绑定this,如下:

1
<button onClick={this.handleClick.bind(this)}>hello</button>

或者直接将箭头函数写在调用处,如下:

1
<button onClick={() => this.handleClick}>hello</button>

这两种方法会有轻微的性能问题,因为每次render函数调用时都会重新分配handleClick这个函数。

推荐第一种方法,简单方便,没有副作用。

JavaScript Object.prototype.groupBy

给定如下对象数组,如何按照type进行分组?

1
2
3
4
5
6
7
const inventory = [
{ name: "asparagus", type: "vegetables", quantity: 5 },
{ name: "bananas", type: "fruit", quantity: 0 },
{ name: "goat", type: "meat", quantity: 23 },
{ name: "cherries", type: "fruit", quantity: 5 },
{ name: "fish", type: "meat", quantity: 22 },
];

最直观的做法如下,遍历数组,然后取出每个对象的type字段,按照哈希表归类的方式进行分组,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function groupBy(arr, key) {
const result = [];
for (const item of inventory) {
const { type } = item;
if (result[type]) {
result[type].push(item);
} else {
result[type] = [item];
}
}

return result;
}

当然,也可以是用reduce函数,注意下面代码中acc的初始值是{},因为我们显示传递了{}给reduce函数。

1
2
3
4
5
6
7
8
9
10
const groupBy = (arr, key) => {
return arr.reduce((acc, item) => {
const group = item[key];
if (!acc[group]) {
acc[group] = [];
}
acc[group].push(item);
return acc;
}, {});
};

最后,你还可以使用groupBy, 只是这个方法比较新( Chrome 117 or later and Node.js 21.0.0 or later),要注意兼容性。

1
2
const result = Object.groupBy(inventory, ({ type }) => type);
console.log(result);

output:

1
2
3
4
5
6
7
8
9
10
11
12
{
vegetables: [ { name: 'asparagus', type: 'vegetables', quantity: 5 } ],
fruit: [
{ name: 'bananas', type: 'fruit', quantity: 0 },
{ name: 'cherries', type: 'fruit', quantity: 5 }
],
meat: [
{ name: 'goat', type: 'meat', quantity: 23 },
{ name: 'fish', type: 'meat', quantity: 22 }
]
}

References:

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy

JavaScript Console

console对象是JavaScript的一个全局对象,它提供了控制台的操作方法和属性。控制台是浏览器提供的一个调试工具,它可以用来输出信息、查看变量的值、查看调用栈等。

colored log

In JavaScript, you can output colored logs to the console using the %c directive in console.log(). Here’s an example:

1
console.log("%cThis is a green text", "color:green; font-size: 18px");

output:
console-green-text
In this example, %c is used as a placeholder for the styles that are specified in the second argument. The text “This is a green text” will be displayed in green color in the console.

You can also specify multiple styles:

1
console.log("%cThis is a blue text on yellow background", "color:blue; background-color:yellow");

output:
alt text

In this example, the text will be blue and the background color will be yellow.

console.dir()

To print a deeply nested object in a more readable way, you can use console.dir().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = {
name: 'Philip',
gender: 'male',
address: {
company: {
name: 'google',
location: {
city: 'shanghai',
country: 'china',
},
},
},
};

console.log(obj);

output: Notice that the location object is printed as [Object] which is unreadable(unreadable in Node.js, it’s OK to run in Browser console).

1
2
3
4
5
{
name: 'Philip',
gender: 'male',
address: { company: { name: 'google', location: [Object] } }
}

Usually, we resolve this by using JSON.stringify() to convert the object to a string.

1
console.log(JSON.stringify(obj));

but this method has a limitation: it only works for objects that are serializable to JSON. If the object contains circular references, it will throw an error.

1
console.dir(obj);

output:

1
2
3
4
5
6
7
8
9
10
{
name: 'Philip',
gender: 'male',
address: {
company: {
name: 'google',
location: { city: 'shanghai', country: 'china' }
}
}
}

JavaScript flatMap

flatMap is the combination of map and flat, It first maps each element using a mapping function, then flattens(only one level) the result into a new array. It’s the same as map followed by flat.

1
arr.flatMap(f); // is equal to arr.map(f).flat();

Note that flatMap only flattening one level

所以,flatMap最多只对二维数组有效,对于多维数组,可以使用arr.map(f).flat(Infinity)

以下代码的作用是,给定一个数组,将数组中每个元素和它的2倍放到一个新数组中,并将所有结果放到一个新数组中。

1
2
3
const arr = [1, 2, 3, 4, 5];
const result = arr.flatMap(x => [x, x * 2]);
console.log(result); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

is equal to

1
2
3
const arr = [1, 2, 3, 4, 5];
const result = arr.map(x => [x, x * 2]).flat();
console.log(result); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

看下面的例子,给定一个字符串数组,其中每个元素是一句话,我们需要将每个句子拆分成单词,并将所有单词放到一个新数组中。

1
2
3
4
5
6
7
8
const arr = [
'It is sunny today',
'I am happy',
'I am learning JavaScript'
];

const result = arr.flatMap(x => x.split(' '));
console.log(result); // ["It", "is", "sunny", "today", "I", "am", "happy", "I", "am", "learning", "JavaScript"]

is equal to

1
2
3
4
5
6
7
8
const arr = [
'It is sunny today',
'I am happy',
'I am learning JavaScript'
];

const result = arr.map(x => x.split(' ')).flat();
console.log(result); // ["It", "is", "sunny", "today", "I", "am", "happy", "I", "am", "learning", "JavaScript"]

Conclusion

So, When to use flatMap? When using Array.prototype.map the callback function usually return an element, but sometimes it returns an array, and you want to flatten the result. Then you can use flatMap to make the code more concise and readable.

当使用Array.prototype.map的回调函数返回数组时,你通常都要使用flatMap.

Authentication vs Authorization

Authentication and authorization are two closely related concepts, but they are not the same. They are both used to protect resources, but they do so in different ways.

In simple terms, authentication is the process of verifying who a user is, while authorization is the process of verifying what they have access to.

Comparing these processes to a real-world example, when you go through security in an airport, you show your ID to authenticate your identity. Then, when you arrive at the gate, you present your boarding pass to the flight attendant, so they can authorize you to board your flight and allow access to the plane.

  • Authentication

    • Authentication is the process of verifying the identity of a user. This is typically done by asking for a username and password, but it can also be done using other methods, such as biometric data or security tokens.
    • The goal of authentication is to ensure that the person accessing a resource is who they claim to be.
  • Authorization

    • Authorization is the process of verifying what a user has access to. This is typically done by checking the user’s permissions and comparing them to the resource they are trying to access.

Conclusion

  • Authentication: 认证
  • Authorization: 授权

以现实中住酒店为例,假设你定好了酒店并来到前台,接待员会要求你出示身份证,以确认你的身份,这就是认证。确认你的身份合法后,他们会给你一张房卡,用这张卡你就可以进入你的房间,这就是授权。

最后的最后

我已经彻底明白认证和授权的区别了,但是,这两个单词实在是太像了,我怎么区分他们呢?

  • 从操作步骤上来说,认证在授权之前。
  • 从单词上来说,如果按照字母顺序排序,authentication 在 authorization 之前。

这样就能记住了,你学会了吗?