文章目录
什么是异步编程
异步编程是一种编程方式,它可以让程序在执行一个可能长时间运行的任务的同时,继续对其他事件做出反应,而不必等待任务完成,直到任务执行完毕之后再来通知你(在js中是通过回调函数的形式)。异步编程可以提高程序的性能和响应能力,避免阻塞和等待。
一般来说,异步编程适用于以下场景(一些I/O密集型操作):
详细来说:
- 不涉及共享资源,或对共享资源只读,即非互斥操作
- 没有时序上的严格关系,或可以通过回调函数、Promise、async/await等方式处理时序逻辑
- 不需要原子操作,或可以通过其他方式控制原子性
- 常用于IO操作(如网络请求、文件读写、数据库操作等)等耗时操作,因为这些操作会比较影响用户体验和程序性能
- 不影响主线程或主程序逻辑的执行
例如,在浏览器中,我们可以使用XMLHttpRequest或fetch API来发起异步的HTTP请求,这样就不会阻塞用户界面的交互。我们可以通过事件监听器或then方法来获取请求的结果,并进行相应的处理。
在Node.js中,我们可以使用fs模块来进行异步的文件操作,这样就不会阻塞主线程的事件循环。我们可以通过回调函数或Promise来获取操作的结果,并进行相应的处理。
JS异步编程概要理解
在js中有两种实现异步的方式:
- 传统的回调函数
- Promise(理解为承诺,承诺在未来的某一个时间返回数据)
因为传统的回调函数的方式容易引起回调地狱,所以我们这里来看看Promise。
我们通过fetch请求一个地址之后,会立马获得一个Promise,我们随后可以调用它的then方法,并传递一个回调函数,如果这个请求在未来被成功完成,那么回调函数会被调起,请求的结果也会以参数的形式传递进来。
但如果光是这样Promise和回调函数就没有什么区别了。Promise的特点是可以用一种链式结构将多个异步操作串联起来:
比如这里的response.json()方法也会返回一个Promise,代表在未来的某个时刻将返回数据转换成为json格式,如果我们想要等它完成之后再执行其他操作,我们就可以在后面追加一个then,然后执行接下来的代码,比如说这里就是将转化后的json打印出来。
同时Promise中还涉及catch异常捕捉以及finally块,也方便了我们的开发工作。
再来说说async、await。它是基于promise之上的语法糖,可以让异步操作更加的简洁明了。
async将函数标名为异步函数,异步函数就是指返回值为Promise对象的函数,比如前面提到的fetch:
在异步函数中我们还可以调用其他的异步函数,不过这个时候我们不需要使用then,而是使用更加简洁的await语法。await会等待Promise完成之后
返回正确的结果。
await虽然看上去会暂停函数的执行,但是在等待的过程中js同样可以处理其他的任务,比如更新界面、运行其他的程序代码等等。这是因为await底层是基于Promise和事件循环机制实现的。
Promise是什么
抽象表达:
- Promise 是一门新的技术(ES6 规范)
- Promise 是 JS 中进行异步编程的新解决方案。(旧方案是单纯使用回调函数)
具体表达:
- 从语法上来说: Promise 是一个构造函数
- 从功能上来说: Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
为什么要用Promise
①指定回调函数的方式更加灵活
- 旧的: 必须在启动异步任务前指定
- promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
②支持链式调用, 可以解决回调地狱问题
-
什么是回调地狱?
- 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件
-
回调地狱的缺点?
- 不便于阅读
- 不便于异常处理
-
解决方案?
- promise 链式调用
-
终极解决方案?
- async/await
Promise的初体验
案例一
场景:点击按钮, 1s 后显示是否中奖(30%概率中奖)
若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券
若未中奖弹出 再接再厉
如果用原来的方式:
//生成随机数
function rand(m,n){
return Math.ceil(Math.random() * (n-m+1)) + m-1;
}
//获取元素对象
const btn = document.querySelector('#btn');
//绑定单击事件
btn.addEventListener('click', function(){
定时器
setTimeout(() => {
//30% 1-100 1 2 30
//获取从1 - 100的一个随机数
let n = rand(1, 100);
//判断
if(n <= 30){
alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券');
}else{
alert('再接再厉');
}
}, 1000);
如果用Promise方法来做:
//Promise 形式实现
// resolve 解决 函数类型的数据
// reject 拒绝 函数类型的数据
const p = new Promise((resolve, reject) => {
setTimeout(() => {
//30% 1-100 1 2 30
//获取从1 - 100的一个随机数
let n = rand(1, 100);
//判断
if(n <= 30){
resolve(n); // 将 promise 对象的状态设置为 『成功』
}else{
reject(n); // 将 promise 对象的状态设置为 『失败』
}
}, 1000);
});
console.log(p);
//调用 then 方法
// value 值
// reason 理由
p.then((value) => {
alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value);
}, (reason) => {
alert('再接再厉, 您的号码为 ' + reason);
});
});
代码解释:
①promise 对象用来封装一个异步操作,所以我们把etTimeout()方法放在了其中。
②Promise实例化要接收一个函数类型的值,而且这个函数有两个形参(最好是命名为resolve,和reject),并且resolve,和reject这两个形参也是函数类型,并且
- 在异步任务成功的时候调用resolve()
- 在异步任务失败的时候调用reject()
而这两个函数的作用就是:
- resolve()将Promise对象的状态变为成功
- reject()将Promise对象的状态变为失败
这种状态的改变,是为了去影响后面then中相应方法的回调。
③Promise对象的then方法接受两个参数,且两个参数都是函数类型的值。其中:
- 第一个函数是对象成功时的回调
- 第二个函数是对象失败时的回调
④前面我们提到过promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值。
我们可以将要返回的值,传给resolve()和 reject()中,然后将then方法中的两个方法传入形参(一般第一个函数的形参为value,第二个函数的形参为reason),通过这两个形参就可以拿到结果值了。
案例二:Promise对Ajax操作的封装
<body>
<div class="container">
<h2 class="page-header">Promise 封装 AJAX 操作</h2>
<button class="btn btn-primary" id="btn">点击发送 AJAX</button>
</div>
<script>
//接口地址 https://api.apiopen.top/getJoke
//获取元素对象
const btn = document.querySelector('#btn');
btn.addEventListener('click', function(){
//创建 Promise
const p = new Promise((resolve, reject) => {
//1.创建对象
const xhr = new XMLHttpRequest();
//2. 初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke');
//3. 发送
xhr.send();
//4. 处理响应结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断响应状态码 2xx
if(xhr.status >= 200 && xhr.status < 300){
//控制台输出响应体
resolve(xhr.response);
}else{
//控制台输出响应状态码
reject(xhr.status);
}
}
}
});
//调用then方法
p.then(value=>{
console.log(value);
}, reason=>{
console.warn(reason);
});
});
</script>
</body>
Promise的基本流程
封装Ajax的请求
<body>
<script>
/**
* 封装一个函数 sendAJAX 发送 GET AJAX 请求
* 参数 URL
* 返回结果 Promise 对象
*/
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的结果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
sendAJAX('https://api.apiopen.top/getJok')
.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
</script>
</body>
Promise 的状态改变
Promise的状态是Promise对象的属性,叫做PromiseState。
他一共有三个值:
- pending 未决定的
- resolved / fullfilled 成功
- rejected 失败
而Promise的状态改变情况只有两种:
- pending 变为 resolved
- pending 变为 rejected
且一个 promise 对象只能改变一次
无论变为成功还是失败, 都会有一个结果数据
成功的结果数据一般称为 value, 失败的结果数据一般称为 reason
Promise对象结果属性
Promise实例对象中还有一个属性PromiseResult
,这个属性中保存着异步任务成功或失败的结果。
这个结果只有两个东西可以进行修改:
- resolve()
- reject()
在这两个函数作出修改之后,在后续的then回调方法中,就又可以把结果给取出来。
Promise的工作流程
Promise的API
Promise 构造函数: Promise (excutor) {}
(1) executor 函数: 执行器 (resolve, reject) => {}
(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}
(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}
说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
Promise.prototype.then 方法: (onResolved, onRejected) => {}
(1) onResolved 函数: 成功的回调函数 (value) => {}
(2) onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象
Promise.prototype.catch 方法: (onRejected) => {}
(1) onRejected 函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖,只能指定失败的回调, 相当于: then(undefined, onRejected)
例如:
<script>
//
let p = new Promise((resolve, reject) => {
// ** 同步调用
// console.log(111);
//修改 promise 对象的状态
reject('error');
});
// console.log(222);
//执行 catch 方法
p.catch(reason => {
console.log(reason);
});
</script>
Promise.resolve 方法
Promise.resolve 方法: (value) => {}
(1) value: 成功的数据 或 promise 对象
说明: ①返回一个成功/失败的 promise 对象
②resolve方法是属于Promise的方法,而不是Promise对象的方法。
例如:
<script>
//
let p1 = Promise.resolve(521);
//如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象,并且其结果为传入的参数
//如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
}));
// console.log(p2);
p2.catch(reason => {
console.log(reason);
})
</script>
如果返回的是错误的Promise对象,那么浏览器会有一个报错,此时可以用catch进行处理。
Promise.reject 方法
Promise.reject 方法: (reason) => {}
(1) reason: 失败的原因
说明: 返回一个失败的 promise 对象,失败的结果为传入的值。
例如:
<script>
// let p = Promise.reject(521);
// let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
//即使传入了一个成功的对象他也只会返回一个错误的对象
console.log(p3);
</script>
Promise.all 方法
Promise.all 方法: (promises) => {}
(1) promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败。如果成功,则其结果为所有成功Promise对象结果组成的数组,如果失败,则其结果为失败的Promise的结果(不是多个失败的结果,因为第一个失败的时候就直接return了,所以只会返回第一个)。
<script>
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
// let p2 = Promise.resolve('Success');
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');
//
const result = Promise.all([p1, p2, p3]);
console.log(result);
</script>
Promise.race 方法
Promise.race 方法: (promises) => {}
(1) promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态
<script>
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);
</script>
如何改变 promise 的状态?
要想修改Promise对象的状态,我们有三种办法:
- 用resolve()函数,使状态由pending --> fullfilled
- 用reject()函数,使状态由pending --> rejected
- 抛出错误,使状态由pending --> rejected
例如:
<script>
let p = new Promise((resolve, reject) => {
//1. resolve 函数
// resolve('ok'); // pending => fulfilled (resolved)
//2. reject 函数
// reject("error");// pending => rejected
//3. 抛出错误
// throw '出问题了';
});
console.log(p);
</script>
一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时都会调用
例如:
<script>
let p = new Promise((resolve, reject) => {
// resolve('OK');
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
</script>
改变 promise 状态和指定回调函数谁先谁后?
- (1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
- (2) 如何先改状态再指定回调?
① 在执行器中直接调用 resolve()/reject()
② 延迟更长时间才调用 then() - (3) 什么时候才能得到数据?
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
promise.then()返回的新 promise 的结果状态由什么决定?
(1) 简单表达: 由 then()指定的回调函数执行的结果决定
(2) 详细表达:
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
例如:
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
// console.log(value);
//1. 抛出错误
// throw '出了问题';
//2. 返回结果是非 Promise 类型的对象
// return 521;
//3. 返回结果是 Promise 对象
// return new Promise((resolve, reject) => {
// // resolve('success');
// reject('error');
// });
}, reason => {
console.warn(reason);
});
console.log(result);
</script>
如果没有return则默认返回undefined
promise 如何串连多个操作任务?
(1) promise 的 then()返回一个新的 promise, 可以利用此特点构成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
例如:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value); //此value为undefined
})
</script>
promise 异常传透
(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
异常处理的常规逻辑,如果在每一层都对应的去处理相应的异常这很麻烦,我们可以简单的把这些异常汇聚到一个范围一起处理。就跟java异常处理中,我们可以把异常往“上”抛,在一个调用者中进行集中的处理。
例如:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 1000);
});
p.then(value => {
// console.log(111);
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
如何终断Promise链
(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
(2) 办法: 在回调函数中返回一个 pendding 状态的 promise 对象(因为then方法只能对状态为fullfilled/rejected的Promise对象进行相应的回调)
例如:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
自定义Promise
定义整体结构
/*
自定义 Promise
*/
(function (window) {
/*
Promise 构造函数
excutor: 内部同步执行的函数 (resolve, reject) => {}
*/
function Promise(excutor) {
}
/*
为 promise 指定成功/失败的回调函数
函数的返回值是一个新的 promise 对象
*/
Promise.prototype.then = function (onResolved, onRejected) {
}
/*
为 promise 指定失败的回调函数
是 then(null, onRejected)的语法糖
*/
Promise.prototype.catch = function (onRejected) {
}
/*
返回一个指定了成功 value 的 promise 对象
*/
Promise.resolve = function (value) {
}
/*
返回一个指定了失败 reason 的 promise 对象
*/
Promise.reject = function (reason) {
}
/*
返回一个 promise, 只有 promises 中所有 promise 都成功时, 才最终成功, 只要有一个失败就直接
失败
*/
Promise.all = function (promises) {
}
/*
返回一个 promise, 一旦某个 promise 解决或拒绝, 返回的 promise 就会解决或拒绝。
*/
Promise.race = function (promises) {
}
// 暴露构造函数
window.Promise = Promise
})(window)
Promise 构造函数的实现
/*
Promise 构造函数
excutor: 内部同步执行的函数 (resolve, reject) => {}
*/
function Promise(excutor) {
const self = this
self.status = 'pending' // 状态值, 初始状态为 pending, 成功了变为
resolved, 失败了变为 rejected
self.data = undefined // 用来保存成功 value 或失败 reason 的属性
self.callbacks = [] // 用来保存所有待调用的包含 onResolved 和 onRejected 回调函数的对象的数组
/*
异步处理成功后应该调用的函数
value: 将交给 onResolve()的成功数据
*/
function resolve(value) {
if(self.status!=='pending') { // 如果当前不是 pending, 直接结束
return
}
// 立即更新状态, 保存数据
self.status = 'resolved'
self.data = value
// 异步调用所有待处理的 onResolved 成功回调函数
if (self.callbacks.length>0) {
setTimeout(() => {
self.callbacks.forEach(obj => {
obj.onResolved(value)
})
})
}
}
/*
异步处理失败后应该调用的函数
reason: 将交给 onRejected()的失败数据
*/
function reject(reason) {
if(self.status!=='pending') { // 如果当前不是 pending, 直接结束
return
}
// 立即更新状态, 保存数据
self.status = 'rejected'
self.data = reason
// 异步调用所有待处理的 onRejected 回调函数
setTimeout(() => {
self.callbacks.forEach(obj => {
obj.onRejected(reason)
})
})
}
try {
// 立即同步调用 excutor()处理
excutor(resolve, reject
} catch (error) { // 如果出了异常, 直接失败
reject(error)
}
}
promise.then()/catch()的实现
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
// 如果 onResolved/onRejected 不是函数, 可它指定一个默认的函数
onResolved = typeof onResolved === 'function' ? onResolved : value => value // 指定返回的 promise 为一个成功状态, 结果值为 value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} // 指定返回的 promise 为一个失败状态, 结果值为 reason
// 返回一个新的 promise 对象
return new Promise((resolve, reject) => {
/*
专门抽取的用来处理 promise 成功/失败结果的函数
callback: 成功/失败的回调函数
*/
function handle(callback) {
// 1. 抛出异常 ===> 返回的 promise 变为 rejected
try {
const x = callback(self.data)
// 2. 返回一个新的 promise ===> 得到新的 promise 的结果值作为返回的promise 的结果值
if (x instanceof Promise) {
x.then(resolve, reject) // 一旦 x 成功了, resolve(value), 一旦 x失败了: reject(reason)
} else {
// 3. 返回一个一般值(undefined) ===> 将这个值作为返回的 promise 的成功值
resolve(x)
}
} catch (error) {
reject(error)
}
}
if (self.status === 'resolved') { // 当前 promise 已经成功了
setTimeout(() => {
handle(onResolved)
})
} else if (self.status === 'rejected') { // 当前 promise 已经失败了
setTimeout(() => {
handle(onRejected)
})
} else { // 当前 promise 还未确定 pending
// 将 onResolved 和 onRejected 保存起来
self.callbacks.push({
onResolved(value) {
handle(onResolved)
},
onRejected(reason) {
handle(onRejected)
}
})
}
})
}
/*
为 promise 指定失败的回调函数
是 then(null, onRejected)的语法糖
*/
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
Promise.resolve()/reject()的实现
/*
返回一个指定了成功 value 的 promise 对象
value: 一般数据或 promise
*/
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
/*
返回一个指定了失败 reason 的 promise 对象
reason: 一般数据/error
*/
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.all/race()的实现
/*
返回一个新的 promise 对象, 只有 promises 中所有 promise 都产生成功 value 时, 才
最终成功, 只要有一个失败就直接失败
*/
Promise.all = function (promises) {
// 返回一个新的 promise
return new Promise((resolve, reject) => {
// 已成功的数量
let resolvedCount = 0
// 待处理的 promises 数组的长度
const promisesLength = promises.length
// 准备一个保存成功值的数组
const values = new Array(promisesLength)
// 遍历每个待处理的 promise
for (let i = 0; i < promisesLength; i++) {
// promises 中元素可能不是一个数组, 需要用 resolve 包装一下
Promise.resolve(promises[i]).then(
value => {
// 成功当前 promise 成功的值到对应的下标
values[i] = value
// 成功的数量加 1
resolvedCount++
// 一旦全部成功
if(resolvedCount===promisesLength) {
// 将所有成功值的数组作为返回 promise 对象的成功结果值
resolve(values)
}
},
reason =>
// 一旦有一个promise产生了失败结果值, 将其作为返回promise对象的失败结果值
reject(reason)
}
)
}
})
}
/*
返回一个 promise,一旦某个 promise 解决或拒绝, 返回的 promise 就会解决或拒绝。
*/
Promise.race = function (promises) {
// 返回新的 promise 对象
return new Promise((resolve, reject) => {
// 遍历所有 promise
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
(value) => { // 只要有一个成功了, 返回的 promise 就成功了
resolve(value)
},
(reason) => { // 只要有一个失败了, 返回的结果就失败了
reject(reason)
}
)
}
})
}
Promise.resolveDelay()/rejectDelay()的实现
/*
返回一个延迟指定时间才确定结果的 promise 对象
*/
Promise.resolveDelay = function (value, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (value instanceof Promise) { // 如果 value 是一个 promise, 取这个promise 的结果值作为返回的 promise 的结果值
value.then(resolve, reject) // 如果 value 成功, 调用resolve(val), 如果 value 失败了, 调用 reject(reason)
} else {
resolve(value)
}
}, time);
})
}
/*
返回一个延迟指定时间才失败的 Promise 对象。
*/
Promise.rejectDelay = function (reason, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(reason)
}, time)
})
}