ES6
Promise 异步
前置知识
同步和异步
同步和异步是一种消息通知机制
同步阻塞: 上一件事做完了,才能做下一件事
A 调用 B,B 处理获得结果,才返回给 A。
A 在这个过程中,一直等待 B 的处理结果,拿到结果, 然后继续往下执行。
异步非阻塞: 上一件事没有做完,就可以做下件事
A 调用 B,无需等待 B 的结果,B 通过状态,通知等来通知 A 或回调函数来处理。
调用结果返回时, 会以消息或回调的方式通知调用者。
在 JS 中正常的代码执行,全部走的都是同步模式,必须拿到一行的执行结果,再执行下一行。
回调地狱
最早我们处理异步消息通知,都是通过回调来处理的。
如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。
fn("first", function () {
fn("second", function () {
fn("third", function () {
console.log("end");
});
});
});
Promise
描述
Promise 对象,就是代表了未来某个将要发生的事件(通常是一个异步操作)。
Promise 不解决异步问题,解决的是异步的写法,避免了层层嵌套的回调函数。
基本语法
new Promise(function (resolve, reject) {});
// resolve(传递给成功的执行函数的数据) 代表异步执行完成,并成功拿到结果
// reject(传递给处理失败的执行函数的数据) 代表异步执行完成,但是没有成功拿到结果
内部状态
- Pending 在等待(异步执行中)
- Fulfilled(标准) | Resolved 执行成功 调用 resolve() 之后改变
- Rejected 执行失败 调用 reject() 之后改变
方法
then(onFulfilled,onRejected)
参数:
onFulfilled
当Promise变成成功状态时,该参数作为回调函数被调用。该函数有一个参数,即resolve传入的参数。
如果传入的 onFulfilled 不是函数,则会在内部被替换为(x) => x ,即原样返回 promise 最终结果的函数。
onRejected
当Promise变成拒绝状态(rejection )时,该参数作为回调函数被调用,该函数有一个参数,即拒绝的原因。
返回值:
当一个Promise成功或者失败,返回函数将被异步调用(由当前的线程循环来调度完成)。
具体的返回值依据以下规则返回:
- 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 如果then中的回调函数没有返回值,那么then返回的Promise将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
- 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
Promise.reject()
Promise.reject(reason) 返回一个状态为 Rejected 的 Promise 对象
参数:
reason 失败原因
Promise.resolve()
Promise.resolve(value) 返回一个状态为 resolved 的 Promise 对象
参数:
value 将被 Promise 对象解析的参数
Promise.catch()
捕获前一个promise抛出的错误
Promise.all(iterable)
接收一个可迭代对象,包装成一个新的Promise实例,当所有Promise都成功的时候,整个Promise.all才成功。
Promise.race(iterable)
接收一个可迭代对象,哪个最先执行完成,则返回其状态(无论成功与否)
Iterator
遍历器(Iterator)就是这样一种机制。
它是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
作用
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。
迭代协议
可迭代协议
要成为可迭代对象, 一个对象必须实现 @@iterator
方法。
即对象(或者它原型链上的某个对象)必须有一个键为 @@iterator
的属性,可通过常量 Symbol.iterator
访问该属性。
迭代器协议
只有实现了next()
方法,一个对象才能成为迭代器。
next():必须返回一个对象,该对象应当有两个属性:
done
如果迭代器可以产生下一个值,则为 false。
如果迭代器已将序列迭代完毕,则为 true。
value
迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
return():for-of提前退出调用,返回{ done: true }
throw():不使用,配合 Generator 函数使用
迭代对象
可被迭代的对象 - 实现了[Symbol.iterator]方法。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
迭代语句
for...in:以原始插入的顺序迭代对象的可枚举属性
for...of:根据迭代对象的迭代器实现迭代数据 实质调用迭代对象的`Symbol.iterator`方法
迭代器实现原理
obj[Symbol.iterator] = function () {
return {
next() {
return {
value: this.i++,
done: false,
};
},
};
};
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定
调用 Iterator 的场景
-
解构赋值
-
扩展运算符
-
yield*
-
接收数组的地方
- for…of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()
- Promise.all()
- Promise.race()
Generator
基本概念
- 形式:Generator 是一个普通函数,两个特征
- function 命令与函数名之间有一个星号
- 函数体内部使用 yield 语句,定义遍历器的每个成员,即不同的内部状态
- 解读:每次调用
next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。
function* fn() {
yield 1;
yield 2;
yield 3;
}
let g = gen();
应用场景
- 异步操作的同步化表达
- 控制流管理
- 部署
Iterator
接口 - 作为有
Iterator
接口的数据结构
ESModule
export 命令
- 默认导出:export default XXX
- 单独导出:export const name = “Abc”
- 按需导出:export { name, sex, age }(推荐)
- 改名导出:export { name as exportName }
export 可以导出多个,export default 只能导出一个
import 命令
- 默认导入:import module from “fileName”
- 整体导入:import * as module from “fileName”
- 按需导入:import { name, sex, age } from “fileName”
- 改名导入:import { name as importName } from “fileName”
- 自执导入:import “fileName”
- 复合导入:import module, { name } from “fileName”
默认导入在前 按需导入在后
导入 export 导出的,命名要保持一致,导入 export default 导出的,命名可以自定义
模块化优点
- 防止作用域污染
- 提高代码的复用性
- 维护成本降低
模块化方案
CommonJS
:用于服务器(动态化依赖)AMD
:用于浏览器(动态化依赖)CMD
:用于浏览器(动态化依赖)UMD
:用于浏览器和服务器(动态化依赖)ESM
:用于浏览器和服务器(静态化依赖)
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个
值的拷贝
,ES6 模块输出的是值的引用。
- CommonJS 模块是
运行时
加载,ES6 模块是编译时
输出接口。 - CommonJS 模块的
require()
是同步加载
模块,ES6 模块的import
命令是异步加载
,有一个独立的模块依赖的解析阶段。
Proxy
概括
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler
参数的写法。
new Proxy()
表示生成一个Proxy
实例target
参数表示所要拦截的目标对象handler
参数也是一个对象,用来定制拦截行为。
实例方法
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 - deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
this 问题
在 Proxy 代理的情况下,目标对象内部的 this
关键字会指向 Proxy 代理。
Reflect
概述
提供拦截 JavaScript 操作的方法。
Reflect 不是一个函数对象,因此它是不可构造的。
静态方法
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
ES8
Async
概述
定义:Generator 函数语法糖
原理:将 Generator 函数和自动执行器 spawn 包装在一个函数里
形式:将 Generator 函数的*
替换成 async
,将 yield
替换成 await
作用:将异步以同步的形式书写
async
函数的语法规则总体上比较简单,难点是错误处理机制。
语法
返回 Promise 对象
async
函数返回一个 Promise 对象。
async
函数内部 return
语句返回的值,会成为 then
方法回调函数的参数。
async
函数内部抛出错误,会导致返回的 Promise 对象变为 reject
状态。抛出的错误对象会被 catch
方法回调函数接收到。
Promise 对象的状态变化
async
函数返回的 Promise 对象,必须等到内部所有 await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return
语句或者抛出错误。
只有 async
函数内部的异步操作执行完,才会执行 then
方法指定的回调函数。
错误处理
如果 await
后面的异步操作出错,那么等同于 async
函数返回的 Promise 对象被 reject
。
await 命令
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。
如果不是 Promise 对象,就直接返回对应的值。
使用注意点
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。- 多个
await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。 await
命令只能用在async
函数之中,如果用在普通函数,就会报错。(ES13 应该可以使用顶层 await 了)
Async 对 Generator 改进
- 内置执行器
- 更好的语义
- 更广的适用性
- 返回值是
Promise
对象
ES9
异步遍历器
语法特点
调用遍历器的 next
方法,返回的是一个 Promise 对象。
await 可以和 for…of 循环一起使用,以串行的方式运行异步操作
Promise.prototype.finally(onFinally)
参数:onFinally
Promise 结束后调用的 Function。
返回值:返回一个设置了 finally 回调函数的 Promise
对象。
finally()
方法返回一个 Promise。
在 promise
结束时,无论结果是 fulfilled
或者是 rejected
,都会执行指定的回调函数。
避免了同样的语句需要在 then()和 catch()中各写一次的情况。
ES11
Promise.allSettled(iterable)
参数:iterable
一个可迭代的对象,例如 Array,其中每个成员都是 Promise
。
返回一个在所有给定的 promise
都已经 fulfilled
或 rejected
后的 promise,并带有一个对象数组,每个对象表示对应的 promise
结果。
简单地说,就是当所有 promise
返回后,无论成功或失败都会返回结果数组。
使用场景比较
Promise.allSettled
返回的结果相互不依赖;Promise.all()
更适合彼此相互依赖或者在其中任何一个reject
时立即结束。
ES12
Promise.any(iterable)
参数:iterable
一个可迭代的对象,例如 Array。
返回值:
接收一个 Promise 可迭代对象,只要其中的一个 promise
成功,就返回那个已经成功的 promise
。全为 reject
,即返回 reject
状态。