Promise学习笔记
Promise学习笔记
学习来源:尚硅谷
笔记参考:努力学习的汪
0 补充知识
0.1 回调函数的例子
关于回调的基础知识,可在ajax学习笔记
中查看。
回调函数定义:
1 | A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. |
- 异步请求的回调函数
1 | $.get('test.html', function(data) { |
- 点击事件的回调函数
1 | btn.addEventListener('click', function() { |
回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调,这些在我们工作中有很多的使用场景
所以其实并不是我们不认识回调函数,而是我们都萦绕在了这个“callback“ 这个词上,当你在一个函数中看到它就会困惑,其实它只是一个形参名字而已。
0.2 js中的同步任务和异步任务
0.2.1 回调函数和异步机制
我们都知道js是单线程的,这种设计模式给我们带来了很多的方便之处,我们不需要考虑各个线程之间的通信,也不需要写很多烧脑的代码,也就是说js的引擎只能一件一件事的去完成和执行相关的操作,所以所有需要执行的事情都像排队一样,等待着被触发和执行,可是如果这样的话,如果在队列中有一件事情需要花费很多的时间,那么后面的任务都将处于一种等待状态,有时甚至会出现浏览器假死现象,例如其中有一件正在执行的一个任务是一个死循环,那么会导致后续其他的任务无法正常执行,所以js在同步机制的缺陷下设计出了异步模式。
在异步执行的模式下,每一个异步的任务都有其自己一个或着多个回调函数,这样当前在执行的异步任务执行完之后,不会马上执行事件队列中的下一项任务,而是执行它的回调函数,而下一项任务也不会等当前这个回调函数执行完,因为它也不能确定当前的回调合适执行完毕,只要引它被触发就会执行。
js的单线程浏览器内核的多线程
浏览器常驻三大线程: js引擎线程,GUI渲染线程,浏览器事件触发线程
浏览器是一个多线程的执行环境,在浏览器的内核中分配了多个线程,最主要的线程之一即是js引擎的线程,同时js事件队列中的异步请求,交互事件触发,定时器等事件都是由浏览器的事件触发线程进行监听的,浏览器的事件触发线程被触发后会把任务加入到js引擎的任务队列中,当js引擎空闲时候就会开始执行该任务。
0.2.2 js的任务
1) 概述
所有的任务分为两种,一种是同步任务,一种是异步任务。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程,而进入”任务队列“(task queue)的任务,只有等主线程任务执行完毕,”任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行。
主要的异步任务有
- Events:javascript各种事件的执行都是异步任务
- setTimeout、setInterval 定时器 特别的如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数
- queueMicrotask 执行微任务
- XMLHttpRequest(也就是 Ajax)
- requestAnimationFrame 类似于定时器,但是是以每一帧为单位
- fetch Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分
- MutationObserver 创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。
- Promise
- async function
这里说到了一个“队列”(即任务队列),该队列放的是什么呢,放的就是setTimeout
中的function,这些function依次加入该队列,即该队列中所有function中的程序将会在该队列以外的所有代码执行完毕之后再以此执行,这是为什么呢?因为在执行程序的时候,浏览器会默认认为setTimeout以及ajax请求这一类的方法都是耗时程序(尽管可能不耗时),将其加入一个队列中,该队列是一个存储耗时程序的队列,在所有不耗时程序执行过后,再来依次执行该队列中的程序。
具体来说,异步运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
例如,setTimeOut函数为异步任务,for循环为同步任务,setTimeOut里的函数为回调函数。执行顺序为:同步优先,异步靠边,回调垫底。所以即使setTimeOut的时间参数是0依然会放到任务队列里,而不是主线程。主线程执行完for循环以后才执行异步任务setTimeOut。另外setTimeout()只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证回调函数一定会在setTimeout()指定的时间执行。
1 | function a() { |
这里a和b为同步任务,会按照顺序优先执行,setTimeout为异步任务,只有等待执行栈中的任务全部执行完毕后,js线程才会执行该任务。
2) js单线程
javascript是单线程。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念——任务队列。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。于是JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。
注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。
js是同步的?是的,单线程,那肯定只能同步(排队)执行咯
js为什么需要异步?如果JS中不存在异步,只能自上而下执行,万一上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着”卡死”,这样就导致了很差的用户体验
1 promise理解和使用
1.1 概念和特点
Promise是异步编程的一种解决方案
,比传统的解决方案——回调函数和事件——更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
通俗讲,Promise是一个许诺、承诺
,是对未来事情的承诺,承诺不一定能完成,但是无论是否能完成都会有一个结果。
Pending
正在做。。。Resolved
完成这个承诺Rejected
这个承诺没有完成,失败了
Promise 用来预定一个不一定能完成的任务,要么成功,要么失败。在具体的程序中具体的体现,通常用来封装一个异步任务,提供承诺结果。
Promise 是异步编程的一种解决方案,主要用来解决回调地狱的问题,可以有效的减少回调嵌套
。真正解决需要配合async/await
。
特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
优点
- 指定回调函数的方式更加灵活
- 旧的方法:必须在启动异步任务前指定
- promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定多个)
- 支持链式调用,可以解决回调地狱问题
缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。和一般的对象不一样,无需调用。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
总结
- Promise 是一门新的技术(ES6 规范)
- Promise 是 JS 中
进行异步编程
的新解决方案 备注:旧方案是单纯使用回调函数 - 从语法上来说: Promise 是一个
构造函数
- 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值
1.2 promise初体验
1.2.1 封装setTimeout异步任务
需求:点击按钮,1s后显示是否中奖(30%概率中奖),弹出相应提示框。
1 |
|
- Promise实现
1 | btn.addEventListener('click', () => { |
执行结果:
添加需求
显示提示时,输出选中的号码。
无论变为成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value
, 失败的结果数据一般称为 reason
,分别作为resolve和reject的形参。
解决:将n作为参数传递。
1 | // 绑定点击事件 |
执行结果:
1.2.2 封装fs读取文件异步任务
需求:读取项目文件夹下的test.txt
的文件内容。
- 回调函数形式
1 | const fs = require('fs'); |
- promise形式
1 | const fs = require('fs'); |
执行结果:
1.2.3 封装ajax异步任务
需求:向某个地址发送ajax请求,获取响应内容
- 回调函数形式
1 |
|
- promise形式
1 | // 绑定点击事件 |
执行结果:
1.2.4 封装自定义异步操作
1) fs封装
需求:封装一个函数 myReadFile 读取文件内容
- 参数: path 文件路径
- 返回: promise 对象
1 | function myReadFile(path) { |
2) ajax封装
需求:封装一个函数 sendAjax 发送GET请求
- 参数: URL
- 返回结果: Promise对象
1 | function sendAjax(url) { |
1.3 util.promisify风格转化
util.promisify
传入一个遵循常见的错误优先的回调风格的函数(即以(err, value) => ...
)回调作为最后一个参数,并返回一个promise的版本。
- 作用:将回调函数风格的异步函数,转化为promise风格的异步函数。可以将函数直接变成promise的封装方式,不用再去手动封装。
例如将fs.readFile
函数转化:
1 | // 引入util模块 |
1.4 promise的状态和结果
promise实例对象中的一个属性PromiseState
,表示该实例对象的状态。
PromiseState
有三种取值:
pending
未决定的resolved / fullfilled
成功rejected
失败
promise 的状态改变只有两种:
- pending => resolved
- pending => rejected
说明: 只有这2种状态改变
,且一个 promise 对象只能改变一次
。
promise实例对象中的一个属性PromiseState
,表示该实例对象成功或失败的结果。
只有resolve
和reject
这两个函数才能修改该属性。
1.5 promise的工作流程
1.6 promise常用API
1.6.1 构造函数
Promise 构造函数: Promise (excutor) {}
executor 函数: 也称为执行器,函数形式:
(resolve, reject) => {}
,其中:resolve 函数: 内部定义,成功时我们调用的函数
value => {}
reject 函数: 内部定义,失败时我们调用的函数
reason => {}
说明: executor 会在 Promise 内部立即同步调用
,是作为同步任务,而不是作为回调函数的形式(异步任务),异步操作在执行器中执行,换句话说Promise支持同步也支持异步操作。即:执行器本身是作为同步操作,执行器内部的操作作为异步操作。
示例
1 | let p =new Promise((resolve, reject) => { |
执行结果:
1.6.2 then方法
Promise.prototype.then 方法:原型 (onResolved, onRejected) => {}
,其中:
onResolved 函数: 成功的回调函数
(value) => {}
onRejected 函数: 失败的回调函数
(reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调。返回一个新的 promise 对象。
1.6.3 catch方法
Promise.prototype.catch 方法: 原型(onRejected) => {}
onRejected 函数: 失败的回调函数
(reason) => {}
- 说明: then()的语法糖, 相当于: then(undefined, onRejected)
异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中
示例
1 | let p =new Promise((resolve, reject) => { |
1.6.4 resolve方法
Promise.resolve 方法: (value) => {}
,属于函数对象,而不是实例对象
value: 成功的数据或 promise 对象
返回一个成功/失败的 promise 对象,直接改变promise状态
- 传入参数:
- 如果为非promise类对象,则返回的结果为成功的promise对象
- 如果为promise类对象,则参数的结果决定了resolve的结果
示例
1 | // 注意创建形式 |
执行结果:
reject方法
Promise.reject 方法: (reason) => {}
- reason: 失败的原因
说明: 返回一个失败的 promise 对象,直接改变promise状态,代码示例同上
1.6.5 all方法
Promise.all 方法: (promises) => {}
promises: 包含 n 个 promise 的数组
返回一个新的 promise, 只有所有的 promise
都成功才成功
, 只要有一个失败了就直接失败
示例
1 | let p1 = new Promise((resolve, reject) => { resolve('success'); }); |
1.6.6 race方法
Promise.race 方法: (promises) => {}
- promises: 包含 n 个 promise 的数组
- 返回一个新的 promise,
第一个完成
的 promise 的结果状态就是最终的结果状态
示例
如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1
,结果是P2
1 | let p1 = new Promise((resolve, reject) => { |
1.7 promise的几个关键问题
①如何改变 promise 的状态?
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(reason): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
②一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时
都会调用,改变状态后,多个回调函数都会调用,并不会自动停止。
1 | let p = new Promise((resolve, reject) => { |
执行结果:
③改变 promise 状态和指定回调函数谁先谁后?
都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
先指定回调再改变状态(
异步
):先指定回调—> 再改变状态 —>改变状态后才进入异步队列执行回调函数1
2
3
4
5
6
7
8
9
10let p = new Promise((resolve, reject) => {
//异步写法,这样写会先指定回调,再改变状态
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(value);
});先改状态再指定回调(
同步
):改变状态 —>指定回调并马上执行
回调1
2
3
4
5
6
7
8let p = new Promise((resolve, reject) => {
//这是同步写法,这样写会先改变状态,再指定回调
resolve('OK');
});
p.then(value => {
console.log(value);
});
如何先改状态再
指定
回调? 注意:指定并不是执行- 在执行器中直接调用 resolve()/reject() —>即,不使用定时器等方法,执行器内直接同步操作
- 延迟更长时间才调用 then() —>即,在
.then()
这个方法外再包一层例如延时器这种方法
什么时候才能得到数据?
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
注:源码中,promise的状态是通过一个默认为padding
的变量进行判断,所以当你resolve/reject
延时(异步导致当then加载时,状态还未修改)后,这时直接进行p.then()会发现,目前状态还是进行中
,所以只是这样导致只有同步操作才能成功。
所以promise将传入的回调函数
拷贝到promise对象实例上,然后在resolve/reject
的执行过程中再进行调用,达到异步的目的。
④promise.then()返回的新 promise 的结果状态由什么决定?
简单表达: 由 then()指定的回调函数执行的结果决定
详细表达:
如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常
1
2
3
4
5
6
7
8
9
10let p = new Promise((resolve, reject) => {
resolve('ok')
});
let res = p.then(value => {
throw 'error';
}, reason => {
console.warn(reason);
});
console.log(res);如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值
1
2
3
4
5let res = p.then(value => {
return 1;
}, reason => {
console.warn(reason);
});如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
1
2
3
4
5
6
7let res = p.then(value => {
return new Promise((resolve, reject) => {
reject('error!!!');
});
}, reason => {
console.warn(reason);
});
⑤promise 如何串连多个操作任务?
- promise 的 then()返回一个新的 promise,可以看成 then()的链式调用
- 通过 then 的链式调用串连多个同步/异步任务,这样就能用
then()
将多个同步或异步操作串联成一个同步队列
1 | let p = new Promise((resolve, reject) => { |
第一个回调函数then与p实例对象进行绑定;p返回一个新的promise对象,它(没有名字)与第二个回调then进行绑定,故输出value值为success
执行结果:
如果再添加一个then回调:
1 | let p = new Promise((resolve, reject) => { |
执行结果:
注意,第二个回调then也返回一个新的promise对象,对于第二个回调then本身,返回的是非 promise 的值undefined
(js函数没有指定return时,默认返回undefined
),因此,新的promise对象与第三个then绑定,其value为返回的undefined
。
⑥promise 异常传透?
- 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调
- 前面任何操作出了异常,都会传到最后失败的回调中处理
1 | let p = new Promise((resolve, reject) => { |
执行结果:
注:可以在每个then()的第二个回调函数中进行err处理,也可以利用异常穿透特性,到最后用catch
去承接统一处理,两者一起用时,前者会生效(因为err已经将其处理,就不会再往下穿透)而走不到后面的catch。
⑦中断 promise 链?
在关键问题2
中,可以得知,当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有3个then(),但其中有条件判断,如当我符合或者不符合第三个then条件时,要直接中断链式调用,不再走下面的then,该如何?
办法:在回调函数中返回一个 pendding
状态的promise 对象
1 | let p = new Promise((resolve, reject) => { |
执行结果:
原因:由关键问题4
可知,第一个then返回的新promise对象为then函数的返回结果(即新new出来的promise对象),且与第二个then回调进行绑定,而这个promise对象状态为pending
,因此无法执行第二个then回调,所以链条就中断了。
2 自定义Promise
2.1 Promise的实例方法实现
2.1.1 初始结构搭建
- 新建html页面
1 |
|
注意,这里的Promise通过引入我们自己定义的promise.js
来全局覆盖原来内置的Promise。
- 新建
promise.js
1 | // 声明构造函数Promise |
这样基本框架就搭建好了,运行没有报错。
2.1.2 resolve和reject实现
- 使用
const self = this;
保存this执行,使function中可以取得当前实例
ps:可以不使用该方法保存,但是下方function需要改为箭头函数
,否则function默认指向是window
之后代码默认使用self
保存this,箭头函数方式将在最后改为class写法时使用
- 默认设置
PromiseState = 'pending'以及 PromiseResult = 'null'
,这就是promise状态基础
1 | // 声明构造函数 |
测试结果:成功时
2.1.3 throw 抛出异常改变状态
- 在2.1.2的基础上进行修改:将执行器放入
try-catch()
中 - 在catch中使用
reject()
修改 promise 对象状态为『失败
』
1 | // 声明构造函数 |
测试代码:
1 | let p = new Promise((resolve, reject) => { |
执行结果:
2.1.4 状态只能修改一次
- 基于2、3代码中resolve和reject方法进修改
- 在成功与失败函数中添加判断
if(self.PromiseState !== 'pending') return;
,如果进入函数时状态不为pending
直接退出,这样就能做到状态只能从pending
改至其他状态且做到只能改一次
1 | function resolve(data) { |
测试代码:
1 | let p = new Promise((resolve, reject) => { |
2.1.5 then方法执行基础回调
- 修改
Promise.prototype.then
方法 - 传入
then(成功回调,失败回调)
,当调用then后,会判断当前this.PromiseState
的状态,当其为成功时调用成功回调
,失败时调用失败回调
1 | let p = new Promise((resolve, reject) => { |
1 | // 添加then方法 |
执行结果:
2.1.6 异步任务 then 方法实现
问题:当第五小节的then运行异步代码
后,执行器内部代码还未返回(因为用了定时器,里面的代码进入了异步队列),所以当下面的.then()运行时:p
为pending
状态,所以根本不会执行resolve与reject方法。
解决:在then中添加判断pending
状态,将当前回调函数保存到实例对象(存到实例上是为了更方便)中,这样后续改变状态时候才调用得到。
测试代码:
1 | let p = new Promise((resolve, reject) => { |
promise.js
代码:
1 | // 声明构造函数 |
2.1.7 指定多个回调
第六小节中保存回调函数的方式有BUG,如果有多个.then()
,后面加载的回调函数会覆盖之前的回调函数,导致最后回调函数有且只有
最后一个。
例如测试代码:
1 | let p = new Promise((resolve, reject) => { |
执行结果:
解决:使用数组
的方式进行存储回调函数,调用时也是用数组循环取出
构造函数中:
1 | this.callbacks = []; |
then方法中:
1 | // 判断pending的状态 |
执行结果:
2.1.8 同步任务then返回结果
在之前的then运行结果中得知,我们使用 [ then ] 后的返回结果是其回调函数的返回结果,而我们需要的返回结果是一个新的promise对象
- 解:所以我们在then中
return new Promise()
,使其得到的是一个新的promise对象
- 解:所以我们在then中
在为
解决问题1
后产生一个新问题:新的promise对象因为没有用rejerect与resolve
方法,导致返回的状态一直是pending
- 解:在新的promise中判断
运行回调函数
后的返回值是什么,然后根据其不同类型给其赋予不同状态 - 即 回调函数中的promise 赋值给then返回值,所以
最终返回状态==回调函数中的新promise状态
- 如果返回值是一个
非promise
对象,返回状态设置为成功 - 如果返回值是一个异常,返回状态设置为失败
- 解:在新的promise中判断
测试代码
1 | let p = new Promise((resolve, reject) => { |
promise.js
1 | // 添加then方法 |
2.1.9 异步任务then返回结果
2.1.10 then方法代码优化
2.1.11 catch方法与异常穿透与值传递
2.2 Promise的静态方法实现
2.2.1 Promise.resolve 封装
2.2.2 Promise.reject 封装
2.2.3 Promise.all 封装
2.2.4 Promise.race封装
2.3 其他优化
2.3.1 回调函数异步执行
2.3.2 class改写promise
3 async和await
- promise —> 异步
await —> 异步转同步
- await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用。
- await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行并且等待 promise 的解决,如果等的是正常的表达式则立即执行
async —> 同步转异步
- 方法体内部的某个表达式使用await修饰,那么这个方法体所属方法必须要用async修饰所以使用awit方法会自动升级为异步方法
3.1 async函数
- 函数的返回值为 promise 对象
- promise 对象的结果由 async 函数执行的返回值决定,和then返回的结果相同
1 | async function main() { |
执行结果:
3.2 await表达式
- await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
- 如果表达式是 promise 对象, await 返回的是 promise 成功的值
- 如果表达式是其它值, 直接将此值作为 await 的返回值
注意:
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
示例
- await 右侧的表达式一般为 promise 对象,但也可以是其它的值,如果表达式是 promise 对象,await 返回的是 promise 成功的值
1 | async function main() { |
- 如果表达式是其它值,直接将此值作为 await 的返回值
1 | async function main() { |
- 如果 await 的 promise 失败了,就会抛出异常,需要通过 try…catch 捕获处理
1 | async function main() { |
3.3 两者的结合案例
- 需求:读取三个文件
原始的回调函数形式:
1 | const fs = require("fs"); |
async + await形式:
1 | const myReadFile = require("util").promisify(fs.readFile); |
- 发送ajax请求
1 | function sendAjax(url) { |