promise 总结
参考
你不知道的 js
https://juejin.cn/post/6844904004007247880#heading-31 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await
起源-回调函数
从 setTimeout 开始,我们知道 js 中的代码并不都是同步执行的。在 node 中,异步类型的 api 也更是常见。
回调函数的出现正是为了解决异步问题。想要等待一些操作完成再做一些操作?只需要将其包装在一个函数里,并且传给主函数,然后在主函数中判断执行的时机就好啦
问题:
- 回调地狱——可读性问题
fs.readFile("1.json", (err, data) => {
fs.readFile("2.json", (err, data) => {
fs.readFile("3.json", (err, data) => {
fs.readFile("4.json", (err, data) => {});
});
});
});
doA(function () {
doB();
doC(function () {
doD();
});
doE();
});
doF();
顺序:doA() => doF() => doB() => doC() => doE() => doD()
尤其是加入错误处理后的代码,(上述 fs 为 node 的 err-first 风格代码)设想一下在读取每个文件后再掺杂一些代码,你还能分清楚当前处于哪一个回调吗? 2. 信任问题 对于第三方提供的含回调的高阶函数,我们只能设计回调函数代码,而回调函数的执行时机、执行内容均无从得知。这种控制反转会导致我们无法信任第三方函数运行的结果。
核心-promise 时期
promise 的出现,正是为了解决上述问题。
简单回顾一下 promise 的基本概念:
状态机:pending、fulfilled、rejected 三种状态,状态只能改变一次
链式:Promise.prototype.then()、Promise.prototype.catch() 和 Promise.prototype.finally()
并发:Promise.all()、Promise.allSettled()、Promise.any()、Promise.race()
回调地狱用优雅的链式调用解决,信任问题用只能改变一次的状态解决,对并发的处理解决竞态问题
promisereadFilePromise("1.json")
.then((data) => {
return readFilePromise("2.json");
})
.then((data) => {
return readFilePromise("3.json");
})
.then((data) => {
return readFilePromise("4.json");
});
为什么 promise 就值得信任?
拿这个例子来讲,我们不用在乎回调的内部执行,只要返回给我们一个 promise 的状态,我们就可以得知其是否运作正常了。
对于竞态问题,promise 提供了方便的并发 api 用以简单处理。同时我们也可以利用 promise 方便的编写相关的函数
Promise 的缺点:
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始 还是即将完成)。
ES7-async await 时期
对于 Promise,其实可以优化的也没有多少了。async await 仅仅是一个语法糖,让我们可以用同步的方式去编写异步代码,某种意义上也更优化了可读性。
相比 promise 的优点:错误处理友好(try catch)、对调试友好
理解 await,可以这样思考:await 后的表达式相当于 promise(expression),而在 await 表达式之后的代码都被放进了这个 promise 的.then()里,也就是被 expression 阻塞,只有等待这个 promise 的状态为 resolved 的时候才可以继续执行后面的代码
async 是一个函数标记,await 会对其后的表达式解包:是 promise 就把其.then 的第一个参数(也就是 resolve 的结果)解出来,不是则返回原值。
其原理是基于 ES6 的 Generator 语法实现的。感兴趣可以去看看,也比较简单
扩展:顶层 await。可以理解为是对模块的异步,配合 async 的是对函数的异步。
通过顶层 await,我们可以实现:
- 动态导入(不堵塞,需要的时候才导入相关依赖)
- 资源初始化(可以用一个 race,同时试图从几个地方加载数据,谁快取谁)
- 依赖回退(A 失败可以进入 catch 分支,从而加载 B)一般用于 CDN