8. async/await
我们都知道使用 Promise 能很好地解决回调地狱的问题,但如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,那有没有比 Promise 更优雅的异步方式呢?那就是async/await!我们一起来揭开它神秘的面纱吧!
8.1 语法
async函数中使用await,那么await这里的代码就会变成同步的了,意思就是说只有等await后面的Promise执行完成得到结果才会继续下去,await就是等待。请看下面的示例:
function timeout () { return new Promise(resolve => { setTimeout(() => { console.log(1); resolve() }, 1000); }) } // 不加async和await是2、1 加了是1、2 async function foo () { await timeout() console.log(2) } foo(); // 1, 2
8.2 使用场景
异步处理的逻辑都是使用同步代码的方式来实现的,而且还支持 try catch 来捕获异常,这感觉就在写同步代码,所以是非常符合人的线性思维的。
async function foo () { try { let response1 = await fetch('https://www.baidu.com/') console.log(response1) let response2 = await fetch('https://juejin.im/') console.log(response2) } catch (err) { console.error(err) } } foo()
8.3 注意点
① await 只能在 async 标记的函数内部使用,单独使用会触发 Syntax error;
② await 后面需要跟异步操作,不然就没有意义了,而且 await 后面的 Promise 对象不必写 then ,因为 await 的作用之一就是获取后面 Promise 对象成功状态传递出来的参数。
8.4 async/await 的缺陷
Async/await 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。
这意味着您的代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。有一种模式可以缓解这个问题——通过将 Promise 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。
四、ES2018(ES9)
9. Object Rest & Spread
9.1 语法
这块代码展示了 spread 语法,可以把 input 对象的数据都拓展到 output 对象,这个功能很实用。需要注意的是,如果存在相同的属性名,只有最后一个会生效。
const input = { a: 1, b: 2, c: 3 } const output = { ...input, d: 4 } console.log(output) // {a: 1, b: 2, c: 3, d: 4}
9.2 注意点
如果属性的值是一个对象的话,该对象的引用会被拷贝,而不是生成一个新的对象。
const obj = { x: { y: 10 } }; const copy1 = { ...obj }; const copy2 = { ...obj }; obj.x.y = "jimmy"; console.log(copy1, copy2); // x: {y: "jimmy"} x: {y: "jimmy"} console.log(copy1.x === copy2.x); // → true
10. for await of
异步迭代器(for-await-of):循环等待每个Promise对象变为resolved状态才进入下一步
10.1 我们知道 for...of 是同步运行的,看如下代码
function timeout (time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time) }, time) }) } async function test () { let arr = [timeout(2000), timeout(1000), timeout(3000),] for (let item of arr) { console.log(Date.now(), item.then(console.log)) } } test()
10.2 ES9 中可以用 for...await...of 的语法来操作
for await of 环等待每个Promise对象变为resolved状态才进入下一步。所有打印的结果为 2000,1000,3000
function timeout (time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time) }, time) }) } async function test () { let arr = [timeout(2000), timeout(1000), timeout(3000),] for await (let item of arr) { console.log(Date.now(), item.then(console.log)) } } test()
11. Promise.prototype.finally()
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
11.1 语法
function test () { return new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) } test().then(res => { console.log(res); }).catch(err => { console.log(err); }).finally(() => { console.log('finally'); })
11.2 使用场景
loading关闭
需要每次发送请求,都会有loading提示,请求发送完毕,就需要关闭loading提示框,不然界面就无法被点击。不管请求成功或是失败,这个loading都需要关闭掉,这时把关闭loading的代码写在finally里再合适不过了
五、ES2019(ES10)
12. Object.fromEntries()
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
12.1 示例1: Object 转换操作
const obj = { name: 'jimmy', age: 18 } const entries = Object.entries(obj) console.log(entries) // [Array(2), Array(2)] // ES10 const fromEntries = Object.fromEntries(entries) console.log(fromEntries) // {name: "jimmy", age: 18}
12.2 示例2: Map 转 Object
const map = new Map(); map.set('name', 'Barry'); map.set('age', 18) console.log(map); // Map(2) {'name' => 'Barry', 'age' => 18} const obj = Object.fromEntries(map) console.log(obj); // {name: 'Barry', age: 18}
12.3 示例3: 过滤
const course = { math: 80, english: 85, chinese: 90 } const res = Object.entries(course).filter(([key, val]) => val > 80); console.log(res); // [ [ 'english', 85 ], [ 'chinese', 90 ] ] console.log(Object.fromEntries(res)); //{english: 85, chinese: 90}
13. Array.prototype.flat()
13.1 语法
let newArray = arr.flat( depth )
depth
可选, 指定要提取嵌套数组的结构深度,默认值为 1。
13.2 Demo
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const arr1 = [0, 1, 2, [3, 4]]; console.log(arr1.flat()); // [0, 1, 2, 3, 4] const arr2 = [0, 1, 2, [[[3, 4]]]]; console.log(arr2.flat(2)); // [0, 1, 2, [3, 4]] //使用 Infinity,可展开任意深度的嵌套数组 var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // `flat()` 方法会移除数组中的空项: var arr5 = [1, 2, , 4, 5]; arr5.flat(); // [1, 2, 4, 5]
14. Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const numbers = [1, 2, 3] numbers.map(x => [x * 2]) // [[2], [4], [6]] numbers.flatMap(x => [x * 2]) // [2, 4, 6]
对比下 map 和 flatMap 的区别
let arr = ['今天天气不错', '', '早上好'] arr.map(s => s.split('')) // [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]] arr.flatMap(s => s.split('')) // ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
15. String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
String.prototype.trimEnd(), 就不介绍了,和下面类似;
let str = ' foo ' console.log(str.length) // 8 str = str.trimStart() // 或str.trimLeft() console.log(str.length) // 5
16. Symbol.prototype.description
const name = Symbol('Barry') console.log(name); // Symbol(Barry) console.log(name.toString()); // Symbol(Barry) console.log(name === 'Symbol(Barry)'); // false console.log(name.toString() === 'Symbol(Barry)'); // true
现在可以通过 description 方法获取 Symbol 的描述:
const name = Symbol('Barry') console.log(name.description); // 'Barry' name.description = "es2" // 只读属性 并不能修改描述符 // 如果没有描述符 输入undefined const age = Symbol(); console.log(age.description); // undefined
17. JSON.stringify() 增强能力
JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
\uD83D\uDE0E emoji 多字节的一个字符 console.log(JSON.stringify('\uD83D\uDE0E')) // 打印出笑脸 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列 console.log(JSON.stringify('\uD83D')) // "\ud83d"
18. 修订 Function.prototype.toString()
以前函数的 toString 方法来自 Object.prototype.toString(), 现在的 Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。以前只会返回这个函数,不包含注释、空格等。
function foo () { // ES2019(ES10) console.log('ES2019(ES10)'); } console.log(foo.toString());