ES6 全详解 let 、 const 、解构赋值、剩余运算符、函数默认参数、扩展运算符、箭头函数、新增方法,promise、Set、class等等
目录
- ES6概念
- ECMAScript6简介
- ECMAScript 和 JavaScript 的关系
- ES6 与 ECMAScript 2015 的关系
- 1、let 、 const 、var 区别
- 2、变量解构赋值
- 1、数组解构赋值
- 2、对象解构赋值
- 3、字符串的解构赋值
- 3、展开剩余运算符
- 1、**展开运算符(...)**
- 2、**剩余运算符(...)**
- 4、函数的拓展
- 函数默认参数
- 箭头函数
- 剩余参数(rest)
- name 属性
- 5、数组的拓展
- 扩展运算符
- isInteger()
- Math.trunc()
- Math.sign()
- Array.from()
- Array.of()
- fill()
- flat()
- flatMap()
- 6、字符串的拓展
- 模版字符串
- includes()
- repeat()
- 7、对象的拓展
- 对象属性和方法的简写
- 属性名表达式
- 方法的 name 属性
- Object.assign()
- Object.is()
- 8、Set数据结构
- `add()`
- `has()`
- `size()`
- `delete()`
- `clear()`
- 把集合转换为数组
- 9、Map
- Map方法
- 10、Symbol()
- 11、class类
- class类概念
- 取值函数(getter)和存值函数(setter)
- 静态方法
- 静态属性
- 12、Promise
- Promise是什么
- Promise使用
- Promise 对象的状态
- Promise对象方法
- `Promise.resolve()`
- `Promise.reject()`
- `Promise.all()`
- `Promise.race()`
- `Promise.allSettled()`
- `Promise.any()`
- 手写Promise
- 13、async与await
- async
- await
- 错误处理
ES6概念
ECMAScript6简介
ECMAScript 6.0,简称 ES6,是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
ECMAScript 和 JavaScript 的关系
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的
ES6 与 ECMAScript 2015 的关系
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等
但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动
标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes
方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”
-
1997年:ECMAScript 1.0
-
1998年:ECMAScript 2.0
-
1999年:ECMAScript 3.0
-
2006年:ECMAScript 4.0 未通过
-
2009年:ECMAScript 5.0
-
2015年:ECMAScript 6.0
-
至今,版本号改用年号的形式
1、let 、 const 、var 区别
var: es6 之前的旧语法
- 声明变量
- 可以重复声明
- var 有预解析,可以先赋值后声明
- var 没有块级作用域
let:es6 语法
- 声明变量
- 不可以重复声明
- let 没有预解析,必须先声明后赋值
- 有块级作用域
- 有暂时性死区
const:es6 语法
- 声明常量
- 不可以重复定义
- 声明后不可以赋值、更新
- 没有预解析
- 有块级作用域
- 有暂时性死区
说明:
- 暂时性死区是指,在代码块内,使用
let
和const
命令声明变量之前,该变量都是不可用的 - 如果在声明之前使用这些变量,就会报错。因此,使用
let
和const
定义的变量一定要在声明后再使用,否则会报错
2、变量解构赋值
1、数组解构赋值
按照位置一一对应赋值
const [name, age, obj] = ['longge', 30, { a: 10 }]
console.log(name, age, obj.a) // longge 30 10let [x, , y] = [1, 2, 3];
console.log(x , y) // 1 3let [head, ...tail] = [1, 2, 3, 4];
console.log(head) // 1
console.log(tail) // [2, 3, 4]
结构不成功,变量的值就等于undefined
let [foo] = []
let [bar, foo] = [1]
// foo的值都是undefined
2、对象解构赋值
- 属性可以无序
- 通过属性名一 一对应,并不是按照位置顺序来对应值
- 可以通过旧属性名:新变量名
const {age: newAge,uname,girlFriend: { username },} = {uname: '李新浩',age: 12,girlFriend: {age: 11,username: 'lisi',},}console.log(uname, newAge) // 李新浩 12console.log(username) // lisi
3、字符串的解构赋值
字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello'
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值
let {length : len} = 'hello'
len // 5
3、展开剩余运算符
1、展开运算符(…)
展开运算符(Spread Operator)是一个用于在数组或对象字面量中插入表达式的语法。它允许开发者在一个数组或对象中展开另一个数组或对象
下面是几个使用展开运算符的示例:
在数组字面量中展开数组:
let arr1 = [1, 2, 3]
let arr2 = [...arr1, 4, 5] // [1, 2, 3, 4, 5]
在函数调用中展开数组:
function sum(a, b, c) { return a + b + c
}
let arr = [1, 2, 3]
console.log(sum(...arr)) // 6
在对象字面量中展开对象:
let obj1 = { a: 1, b: 2 }
let obj2 = {...obj1, c: 3} // {a: 1, b: 2, c: 3}
在模板字符串中展开对象:
let obj = { a: 1, b: 2 }
console.log(`${obj.a} ${obj.b} ${obj.c}`) // 1 2 undefined
console.log(`${...obj}`) // "1 2 undefined"
注意,展开运算符只能用于数组或对象字面量的初始化,不能在运行时动态添加元素到数组或对象。
2、剩余运算符(…)
用于赋值号左边或函数形参
是个真数组,箭头函数没有arguments,但可以使用剩余运算符接收动态的参数
示例:
const [a, ...args] = [1, 2, 3, 4]console.log(args) // [2,3,4]
const fn = (...args) => {console.log(args)let sum = 0args.forEach((item) => {sum += item})return sum}console.log(fn(1)) // 1console.log(fn(1, 2)) // 3console.log(fn(1, 2, 3)) // 6
4、函数的拓展
函数默认参数
ES6 函数可以有默认参数,当函数接收了参数,会覆盖默认值
function fn(a = 0, b = 0) { // 函数的形参可以加默认参数0return a + b}console.log(fn()) // 0console.log(fn(1,1)) // 2console.log(fn.name) // fn (name属性可以访问函数名)
箭头函数
- 箭头函数没有this,它内部this由所处作用域(上下文)决定,
call/apply/bind
也不能改变箭头函数this
- 箭头函数没有arguments,普通函数有,但是可以使用剩余运算符…
- 箭头函数不能new(不能实例化)
- 箭头函数没有函数提升
const fun = () => {console.log(this)// console.log(arguments) // 报错 箭头函数没有arguments}
当只有一条return语句,{ }和return可以一起省略,不能只省略一个
const getSum = (x, y) => {return x + y
}
const getSum = (x, y) => x + y
console.log(getSum(3, 4))
形参只有一个,小括号可以省略,其余情况全部要加()
const f2 = (x) => x * 2 // (x)=> {return x *2}
const f2 = x => x * 3
剩余参数(rest)
如果你需要在箭头函数中使用类似 arguments
的功能,可以使用剩余参数(rest)
const fn = (...args) => { for (let arg of args) { console.log(arg)}
}; fn(1, 2, 3) // 输出 1, 2, 3
name 属性
函数的name
属性,返回该函数的函数名
function foo() {}
foo.name // "foo"
5、数组的拓展
Array.isArray()
Array.from()
Array.of()
Array.flat()
数组扁平化
扩展运算符
let arr1 = [1,2,3]
let arr2 = [4,5,6]
// 合并数组
console.log([...arr1,...arr2]) // [1,2,3,4,5,6]
isFinite()
,isNaN()
,Number()
方法
-
减少全局性方法,使得语言逐步模块化
-
与传统的全局方法相比,
isFinite()
和isNaN()
的区别在于,传统方法先调用Number()
将非数值的值转为数值,再进行判断 -
而这两个新方法只对数值有效,
Number.isFinite()
对于非数值一律返回false -
Number.isNaN()
只有对于NaN才返回true,非NaN一律返回false
let num1 = Number.isFinite(100) //true
let num4 = Number.isFinite("100") //false
let num1 = Number.isNaN(100) // false
let num2 = Number.isNaN(NaN) //true
let num3 = Number.isNaN("kerwin") //false
let num4 = Number.isNaN("100") // false
isInteger()
用来判断一个数值是否为整数
let num1 = Number.isInteger(1000) // true
let num2 = Number.isInteger(1000.0) //true
let num3 = Number.isInteger("abc") //false
let num4 = Number.isInteger("1000") // false
Math.trunc()
将小数部分抹掉,返回一个整数
console.log(Math.trunc(1.3)) //1
console.log(Math.trunc(1.9))// 1
console.log(Math.trunc(-1.5)) //-1
console.log(Math.trunc(-1.2))//-1
Math.sign()
正数返回1,负数返回-1,正0返回0,负0返回-0,非数字返回NaN
Math.sign(-100) // -1
Math.sign(100) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign("abc") // NaN
Array.from()
将类数组对象转换为真正数组
//将类数组的对象,转为真数组
let arrayLike = {'0': 'a','1': 'b','2': 'c',length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
将字符串转为真数组
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
Array.of()
将一组值转化为数组,即新建数组
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
fill()
fill 充满
使用自己想要的参数替换原数组内容,会改变原来的数组
参数 | 描述 |
---|---|
value | 必需,填充的值。 |
start | 可选,开始填充位置。 |
end | 可选,停止填充位置 (默认为 array.length),到end为止之前结束 |
let arr1 = ['a', 'b', 'c']console.log(arr1.fill(7)) // [7, 7, 7] 始末位置不写,会将所有值替换console.log(arr1) // [7, 7, 7] 会改变原数组let arr2 = new Array(3).fill(7) console.log(arr2) // [7, 7, 7] 将创建的空数组中值,全部替换
let arr = ['a', 'b', 'c']
console.log(arr.fill(7,1,2)) // ['a', 7, 'c']//上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象
flat()
- flat(depth),depth指定要提取嵌套数组的结构深度,默认值为 1
- 按照一个可指定的深度递归遍历数组,将所有元素与遍历到的子数组中的元素合并为一个新数组返回
- 该方法返回一个新数组,对原数据没有影响
let arr1 = [1, 2, [3, 4]]
arr1.flat()
// [1, 2, 3, 4]let arr2 = [1, 2, , 4, 5].flat()
// [1, 2, 4, 5] 如果原数组有空位,flat()方法会跳过空位let arr3 = [1, 2, [3, 4, [5, 6]]]
arr2.flat()
// [1, 2, 3, 4, [5, 6]]let arr4 = [1, 2, [3, 4, [5, 6]]]
arr3.flat(2)
// [1, 2, 3, 4, 5, 6]//使用 Infinity,可展开任意深度的嵌套数组
let arr5 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]
arr4.flat(Infinity)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
flatMap()
- 它与
map()
连着深度值为 1 的flat()
几乎相同,但flatMap()
通常在合并成一种方法的效率稍微高一些 flatMap()
方法返回新数组,不改变数据- 其中每个元素都是回调函数的结果,并且结构深度 depth 值只能为 1,只能展开一层数组
// 语法
array.flatMap(function (item[, index[, array]]{}[,thisArg])
// 当前数组成员、当前数组成员的下标(从0开始)、原数组、this指向
let obj = [{name: "A",list: ["鞍山", "安庆", "安阳"]},{name: "B",list: ["北京", "保定", "包头"]}]
console.log(obj.flatMap(item => item.list)) // ['鞍山', '安庆', '安阳', '北京', '保定', '包头']
6、字符串的拓展
新增:
startsWith()
endsWith()
repeat()
match()
search()
模版字符串
-
模板字符串(template string)是增强版的字符串,用反引号(``)标识
-
字符串中可以出现换行符
-
可以使用 ${xxx} 形式输出变量
let str = `helloworld
`// 当遇到字符串与变量拼接的情况使用模板字符串
includes()
判断字符串中是否存在指定字符
let myname = "hello world"console.log(myname.includes("e")) // true
console.log(myname.startsWith("k")) // false
console.log(myname.endsWith("w")) // true
repeat()
repeat()方法返回一个新字符串,表示将原字符串重复n次
let str = "hello world"console.log(str.repeat(3)) // hello worldhello worldhello world
console.log(str.repeat(0)) // "" // 参数如果是小数,会向下取整
console.log(str.repeat(3.9)) // hello worldhello worldhello world
console.log(str.repeat("3")) // hello worldhello worldhello world
7、对象的拓展
对象属性和方法的简写
// es5定义对象let obj = {name:name,age:age,getName:function(){return this.name}}// es6定义对象可以简写let obj1={name, //当对象属性名与变量名一致时候可以这样简写age,getName(){ //定义方法时可以省略掉function关键字和冒号return this.name}}
属性名表达式
ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内
let lastWord = 'last word'
const a = {'first word': 'hello',[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"obj['a' + 'bc'] = 123
方法的 name 属性
函数的name
属性,返回函数名。对象方法也是函数,因此也有name
属性。
const person = {sayName() {console.log('hello!')},
};
person.sayName.name // "sayName"
Object.assign()
第一个参数是目标对象,后面可以跟一个或多个源对象作为参数
Object.assign(target, object1,object2,......)
// 目标对象、后面都是源对象
const obj1 = {
name: "李四"
};
const obj2 = {
name:"张三"
};
const obj3 = {
age:18
};
Object.assign(obj1, obj2, obj3);
//obj1 {name: '张三', age: 18}
Object.is()
用来比较两个值是否严格相等,与(===)基本类似
Object.is("q","q") // true
Object.is(1,1) // true
Object.is([1],[1]) // false
Object.is({q:1},{q:1}) // false
与(===)的区别
//一是+0不等于-0
Object.is(+0,-0) //false
+0 === -0 //true
//二是NaN等于本身
Object.is(NaN,NaN) //true
NaN === NaN //false
8、Set数据结构
- 它类似于数组,但是成员的值都是唯一的,没有重复的值
Set
本身是一个构造函数,用来生成 Set 数据结构- 可利用set集合元素的不可重复性,进行数组去重
- 可传入(数组、字符串、其他
Set
对象、等可迭代对象)
示例:
// 用于从数组中删除重复元素
const numbers = [1,1,2,2,2,3,3,3,3]
console.log([...new Set(numbers)]) // [1,2,3]const arr = [1, 2, 2, 3, 4, 4, 5]
const set = new Set(arr)
console.log(set) // Set { 1, 2, 3, 4, 5 } 返回一个对象实例// 去除重复字符
const str = 'hello'
const set = new Set(str)
console.log([...set]) // ['h', 'e', 'l', 'o']
add()
- 如果该 Set 实例对象中没有相同值的元素,Set 实例的
add()
方法会在该集合中插入一个具有指定值的新元素 - 返回添加了值的
Set
对象
// 语法
add(value)
示例:
const mySet = new Set()
mySet.add(1)
mySet.add(1)
mySet.add(5).add("some text") // 可以链式调用
console.log(mySet) // Set [1, 5, "some text"]
has()
Set
实例的 has()
方法返回一个布尔值来指示对应的值是否存在于该集合中
// 语法
has(value) // value 要测试是否存在于 Set 对象中的值
示例:
const set1 = new Set([1, 2, 3, 4, 5])
console.log(set1.has(1)) // true
console.log(set1.has(5)) // true
console.log(set1.has(6)) // false
size()
Set
实例的 size
访问器属性将返回该集合中去除了重复元素的个数
示例:
const set1 = new Set()
const object1 = {}set1.add(42)
set1.add('forty two')
set1.add('forty two')
set1.add(object1)console.log(set1.size) // 3 空对象也算长度
delete()
删除某个值,返回一个布尔值,表示删除是否成功
clear()
清除所有成员,没有返回值
把集合转换为数组
const set1 = new Set()
const object1 = {}set1.add(42)
set1.add('forty two')
set1.add('forty two')
set1.add(object1)
console.log(Array.from(set1)) // [42, 'forty two', {}] Array.from()把集合转成数组
9、Map
- Object 对象只接受字符串作为键名,提供了“字符串—值”的对应
- 而 Map “键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,提供了“值—值”的对应
Map方法
Map.prototype.delete()
删除键值对
Map.prototype.get()
获取键值
Map.prototype.has()
判断是否有某个键
Map.prototype.set()
设置键值对
Map.prototype.size()
获取键值对的个数
Map.prototype.entries()
获取所有键值对
Map.prototype.values()
获取所有值
Map.prototype.keys()
获取所有键
Map.prototype.forEach()
遍历
Map.prototype.clear()
清空所有键值对
示例1:
const myMap = new Map()
myMap.set("bar", "baz")
myMap.set(1, "foo")console.log(myMap) // Map(2) {'bar' => 'baz', 1 => 'foo'}
console.log(myMap.size) // 2
console.log(myMap.has("bar")) // truemyMap.clear()console.log(myMap.size) // 0
console.log(myMap.has("bar")) // false
示例2:
const m = new Map()
const o = {p: 'Hello World'}
m.set(o, 'content') // 对象o作为m的键
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
10、Symbol()
产生独一无二的值
// 产生独一无二的值 Symbolconst s1 = Symbol('a')const s2 = Symbol('a')console.log(s1 === s2) // falseconsole.log(typeof s1) // 'symbol'
11、class类
class类概念
ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,ES6 的类,完全可以看作构造函数的另一种写法
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加
// ES5生成实例对象的传统方法是通过构造函数
function Point(x, y) {this.x = xthis.y = y
}
Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')'
}
let p = new Point(1, 2)
// 上面的代码用 ES6 的class改写,就是下面这样
class Point {constructor(x, y) {this.x = xthis.y = y}//方法前面不需要加上function这个关键字toString() {return '(' + this.x + ', ' + this.y + ')'}
}
//使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致
let p = new Point(1, 2)
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面
class Point {constructor() {// ...}toString() {// ...}toValue() {// ...}
}
// 等同于
/*Point.prototype = {constructor() {},toString() {},toValue() {},
}*/
取值函数(getter)和存值函数(setter)
在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {constructor() {// ...}get prop() {return 'getter';}set prop(value) {console.log('setter: '+value);}
}let inst = new MyClass();inst.prop = 123;
// setter: 123inst.prop
// 'getter'
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
class Foo {static classMethod() {//如果静态方法包含this关键字,这个this指的是类,而不是实例。console.log(this)}
}Foo.classMethod() // 'hello'var foo = new Foo();
foo.classMethod()//报错
静态属性
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性
class Foo {
}Foo.prop = 1
Foo.prop // 1
目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性
12、Promise
Promise是什么
- Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大
- ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象
- 指定回调函数方式更灵活易懂
- 解决异步回调地狱的问题
什么是回调地狱:
- 当一个回调函数嵌套一个回调函数的时候,就会出现一个嵌套结构,当嵌套的多了就会出现回调地狱的情况
- 缺点:可读性差,外层无法捕获内层的异常,耦合严重,牵一发动全身
例如:发送三个 ajax 请求
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
// 这样就出现了多层嵌套,就出现了回调地狱
ajax({url: '我是第一个请求',success (res) {// 现在发送第二个请求ajax({url: '我是第二个请求',data: { a: res.a, b: res.b },success (res2) {// 进行第三个请求ajax({url: '我是第三个请求',data: { a: res2.a, b: res2.b },success (res3) { console.log(res3) }})}})}
})
回调地狱,其实就是回调函数嵌套过多导致的
当代码成为这个结构以后,已经没有维护的可能了
Promise使用
语法:
new Promise(function (resolve, reject) {// resolve 表示成功的回调// reject 表示失败的回调
}).then(function (res) {// 成功的函数
}).catch(function (err) {// 失败的函数
})
Promise 对象的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)
这三种的状态的变化途径只有两种。
从“未完成”到“成功”
从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise对象方法
Promise 是一个对象,也是一个构造函数
Promise.resolve()
Promise.resolve()
方法将现有对象转为 Promise 对象
Promise.resolve('kerwin')
// 等价于
new Promise(resolve => resolve('kerwin'))
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) => reject('error'))
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
p的状态由p1,p2,p3 决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.allSettled()
Promise.allSettled()
方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
const promises = [ ajax('/200接口'), ajax('/401接口') ];Promise.allSettled(promises).then(results=>{// 过滤出成功的请求results.filter(item =>item.status === 'fulfilled');过滤出失败的请求results.filter(item=> item.status === 'rejected');
})
Promise.any()
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
手写Promise
/** @作者: kerwin*/
function KerwinPromise(executor) {this.status = "pending";this.result = undefined;this.cb = []var _this = this;function resolve(res) {if (_this.status !== "pending") return;// console.log(_this)_this.status = "fulfilled"_this.result = res;_this.cb.forEach(item => {item.successCB && item.successCB(_this.result)});}function reject(res) {if (_this.status !== "pending") return;// console.log("reject")_this.status = "rejected"_this.result = res;_this.cb.forEach(item => {item.failCB && item.failCB(_this.result)});}executor(resolve, reject)
}KerwinPromise.prototype.then = function (successCB, failCB) {if(!successCB){successCB = value=>value}if(!failCB){failCB = error=>error}// successCB()return new KerwinPromise((resolve, reject) => {if (this.status === "fulfilled") {var result = successCB && successCB(this.result)// console.log(result);if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {resolve(result);}}if (this.status === "rejected") {var result = failCB && failCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {reject(result);}}if (this.status === "pending") {//收集回调this.cb.push({successCB: () => {var result = successCB && successCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {resolve(result);}},failCB: () => {var result = failCB && failCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {reject(result);}}})}})
}KerwinPromise.prototype.catch= function(failCB){this.then(undefined,failCB)
}
13、async与await
async
async 函数,是个语法糖,使得异步操作变得更加方便
- 更好的语义
- 返回值是 Promise
async function test(){}
test()
await
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function test(){var res1 = await ajax("http://localhost:3000/news1")var res2 = await ajax("http://localhost:3000/news2")return res2
}
test().then(res=>{console.log("返回结果",res)
}).catch(err=>{console.log("err",err)
})
错误处理
try{var res1 = await ajax("http://localhost:3000/news1")var res2 = await ajax("http://localhost:3000/news2")
}catch(err){console.log("err",err)
}
相关文章:

ES6 全详解 let 、 const 、解构赋值、剩余运算符、函数默认参数、扩展运算符、箭头函数、新增方法,promise、Set、class等等
目录 ES6概念ECMAScript6简介ECMAScript 和 JavaScript 的关系ES6 与 ECMAScript 2015 的关系 1、let 、 const 、var 区别2、变量解构赋值1、数组解构赋值2、对象解构赋值3、字符串的解构赋值 3、展开剩余运算符1、**展开运算符(...)**2、**剩余运算符(...)** 4、函数的拓展函…...

c++ - 类的默认成员函数
文章目录 前言一、构造函数二、析构函数三、拷贝构造函数四、重载赋值操作符五、取地址及const取地址操作符重载 前言 默认成员函数是编译器自动生成的,也可以自己重写,自己重写之后编译器就不再生成,下面是深入了解这些成员函数。 一、构造…...
Java哈希查找(含面试大厂题和源码)
哈希查找(Hash Search)是一种基于哈希表(Hash Table)的数据查找方法。哈希表通过使用哈希函数将键(Key)映射到表中的位置来存储数据,从而实现快速的数据访问。哈希查找的效率通常取决于哈希函数…...

c++中常用库函数
大小写转换 islower/isupper函数 char ch1 A; char ch2 b;//使用islower函数判断字符是否为小写字母 if(islower(ch1)){cout << ch1 << "is a lowercase letter." << end1; } else{cout << ch1 << "is not a lowercase lette…...

Scrapy框架 进阶
Scrapy框架基础Scrapy框架进阶 【五】持久化存储 命令行:json、csv等管道:什么数据类型都可以 【1】命令行简单存储 (1)语法 Json格式 scrapy crawl 自定义爬虫程序文件名 -o 文件名.jsonCSV格式 scrapy crawl 自定义爬虫程…...

ubuntu22安装snipaste
Ubuntu 22.04 一、Snipaste 介绍和下载 Snipaste 官网下载链接: Snipaste Downloads 二、安装并使用 Snipaste # 1、进入Snipaste-2.8.9-Beta-x86_64.AppImage 目录(根据自己下载目录) cd /home/jack/Downloads/softwares/AppImage# 2、Snipaste-2.8.9-…...

spring-cloud微服务openfeign
Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Nacos,从而使得Feign的使用更加方便 优势,openfeign可以做到使用HTTP请求远程服务时就像洞用本地方法一样的体验,开发者完全感…...

小程序变更主体需要多久?
小程序迁移变更主体有什么作用?小程序迁移变更主体的好处有很多哦!比如可以获得更多权限功能、公司变更或注销时可以保证账号的正常使用、收购账号后可以改变归属权或使用权等等。小程序迁移变更主体的条件有哪些?1、新主体必须是企业主体&am…...

19 Games101 - 笔记 - 相机与透镜
**19 ** 相机与透镜 目录 摘要一 照相机主要部分二 小孔成像与视场(FOV)三 曝光(Exposure)四 景深(Depth of Field)总结 摘要 虽说照相机与透镜属于相对独立的话题,但它们的确是计算机图形学当中的一部分知识。在过往的十多篇笔记中,我们学习的都是如…...

Flink入门学习 | 大数据技术
⭐简单说两句⭐ ✨ 正在努力的小新~ 💖 超级爱分享,分享各种有趣干货! 👩💻 提供:模拟面试 | 简历诊断 | 独家简历模板 🌈 感谢关注,关注了你就是我的超级粉丝啦! &…...

Arthas实战教程:定位Java应用CPU过高与线程死锁
引言 在Java应用开发中,我们可能会遇到CPU占用过高和线程死锁的问题。本文将介绍如何使用Arthas工具快速定位这些问题。 准备工作 首先,我们创建一个简单的Java应用,模拟CPU过高和线程死锁的情况。在这个示例中,我们将编写一个…...

HTML制作跳动的心形网页
作为一名码农 也有自己浪漫的小心思嗷~ 该网页 代码整体难度不大 操作性较强 祝大家都幸福hhhhh 效果成品: 全部代码: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD><TITLE> 一个…...

如何在Odoo 17 销售应用中使用产品目录添加产品
Odoo,作为一个知名的开源ERP系统,发布了其第17版,新增了多项功能和特性。Odoo 17包中的一些操作简化了,生产力提高了,用户体验也有了显著改善。为了为其用户提供新的和改进的功能,Odoo不断进行改进和增加新…...
为什么pdf拆分出几页之后大小几乎没有变化
PDF 文件的大小在拆分出几页之后几乎没有变化可能有几个原因: 图像压缩: 如果 PDF 文件中包含图像,而这些图像已经被压缩过,拆分后的页面依然会保留这些压缩设置,因此文件大小可能不会显著变化。 文本和矢量图形: PDF 文件中的文…...

如何在 VM 虚拟机中安装 OpenEuler 操作系统保姆级教程(附链接)
一、VMware Workstation 虚拟机 若没有安装虚拟机的可以参考下篇文章进行安装: 博客链接https://eclecticism.blog.csdn.net/article/details/135713915 二、OpenEuler 镜像 点击链接前往官网 官网 选择第一个即可 三、安装 OpenEuler 打开虚拟机安装 Ctrl …...

(六)PostgreSQL的组织结构(3)-默认角色和schema
PostgreSQL的组织结构(3)-默认角色和schema 基础信息 OS版本:Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本:16.2 pg软件目录:/home/pg16/soft pg数据目录:/home/pg16/data 端口:57771 默认角色 Post…...
DockerFile定制镜像
dockerfile 简介 Dockerfile 是⼀个⽤来构建镜像的⽂本⽂件,⽂本内容包含了⼀条条构建镜像所需的指令和 说明,每条指令构建⼀层,最终构建出⼀个新的镜像。 docker镜像的本质是⼀个分层的⽂件系统 centos的iso镜像⽂件是包含bootfs和rootfs…...

Java8中JUC包同步工具类深度解析(Semaphore,CountDownLatch,CyclicBarrier,Phaser)
个人主页: 进朱者赤 阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名) 引言 在Java中,并发编程一直是一个重要的领域,而JDK 8中的java.u…...
岛屿个数(dfs)
[第十四届蓝桥杯省B 岛屿个数] 小蓝得到了一副大小为 M N MN MN 的格子地图,可以将其视作一个只包含字符 0 0 0(代表海水)和 1 1 1(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛…...
【C++造神计划】运算符
1 赋值运算符 赋值运算符的功能是将一个值赋给一个变量 int a 5; // 将整数 5 赋给变量 a 运算符左边的部分叫作 lvalue(left value),右边的部分叫作 rvalue(right value) 左边 lvalue 必须是一个变量 右边 rval…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...

6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...

Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...