目录
- 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 10
let [x, , y] = [1, 2, 3];
console.log(x , y) // 1 3
let [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) // 李新浩 12
console.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 = 0
args.forEach((item) => {
sum += item
})
return sum
}
console.log(fn(1)) // 1
console.log(fn(1, 2)) // 3
console.log(fn(1, 2, 3)) // 6
4、函数的拓展
函数默认参数
ES6 函数可以有默认参数,当函数接收了参数,会覆盖默认值
function fn(a = 0, b = 0) { // 函数的形参可以加默认参数0
return a + b
}
console.log(fn()) // 0
console.log(fn(1,1)) // 2
console.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 = `
hello
world
`
// 当遇到字符串与变量拼接的情况使用模板字符串
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")) // true
myMap.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()
产生独一无二的值
// 产生独一无二的值 Symbol
const s1 = Symbol('a')
const s2 = Symbol('a')
console.log(s1 === s2) // false
console.log(typeof s1) // 'symbol'
11、class类
class类概念
ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,ES6 的类,完全可以看作构造函数的另一种写法
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加
// ES5生成实例对象的传统方法是通过构造函数
function Point(x, y) {
this.x = x
this.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 = x
this.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: 123
inst.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)
}