JavaScript中的异步机制可以分为以下几种:
- 回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。
- Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
- generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。
- async 函数 的方式,
async函数是generator和promise实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
回调函数
什么是回调函数?
JavaScript中的回调函数是指在某个函数执行完毕后,调用另一个函数作为参数传递进去,并在后续的程序中被调用执行的函数。回调函数在JavaScript中广泛应用于事件处理、异步编程和Ajax等场景。
- 就是把函数 A 当作参数传递到 函数 B 中
- 在函数 B 中以行参的方式进行调用
function a(callback) {
callback()
}
function b() {
console.log('我是函数 b')
}
a(b)
为什么需要回调函数?
- 当我们执行一个异步的行为的时候,我们需要在一个异步行为执行完毕之后做一些事情
- 那么,我们是没有办法提前预知这个异步行为是什么时候完成的
- 我们就只能以回调函数的形式来进行
- 就比如我们刚刚封装过的那个
ajax函数里面的success - 我们并不知道
ajax请求什么时候完成,所以就要以回调函数的形式来进行
回调地狱
-
当一个回调函数嵌套一个回调函数的时候 就会出现一个嵌套结构
-
当嵌套的多了就会出现回调地狱的情况
-
比如我们发送三个 ajax 请求
-
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
ajax({
url: '我是第一个请求',
success (res) {
// 现在发送第二个请求
ajax({
url: '我是第二个请求',
data: { a: res.a, b: res.b },
success (res2) {
// 进行第三个请求
ajax({
url: '我是第三个请求',
data: { a: res2.a, b: res2.b },
success (res3) {
console.log(res3)
}
})
}
})
}
})
回调地狱,其实就是回调函数嵌套过多导致的
Promise
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise的实例有三个状态:
-
-
Pending(进行中)
-
Resolved(已完成)
-
Rejected(已拒绝)
-
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
Promise的实例有两个过程:
-
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒绝)
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise对象的基本用法
创建Promise实例对象
使用new Promise()
const promise = new Promise((resolve,reject)=>{
resolve(); //成功
reject(); // 失败/异常
}).then(req=>{
// fullfiled状态执行此部分
}).catch(err=>{
//rejected状态执行此部分
}).finally(()=>{
// 只要从pending状态改变就会执行此部分
})
一般情况下都会使用new Promise()来创建promise对象,但是也可以使用Promise.resolve()和 Promise.reject()这两个方法:
Promise.resolve()
Promise.resolve(value)的返回值也是一个Promise实例对象,可以对返回值进行.then调用,代码如下:
Promise.resolve('success').then(req=>{
// 成功后执行的代码
})
resolve('success')代码中,会让Promise实例对象进入resolve状态,并将参数"success"传递给后面的then所指定的fullfiled函数.
**then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数可以省略。
**then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
Promise.reject()
Promise.resolve(value)的返回值也是一个Promise实例对象,可以对返回值进行.catch调用,代码如下:
Promise.reject(new Error('我报错了!'))
Promise.all([...个 promise 实例对象])
- 当
...个 promise 实例对象都走resolve,则 最终走then,then的回调函数的res是数组,数组的每个元素是...个 promise 实例对象的resolve的值
- 当
...个 promise 实例对象,只要 有一个 走reject,则 立刻走catch,(不走then了),
catch的回调函数的 err 的值是reject的值。
Promise.race([...个 promise 实例对象])
当 race数组中 第一个 Promise 实例对象,改变了 pending 的状态, 则立刻停止数组中 剩下的Promise 实例对象 的实行。
情况1:若 Promise 实例对象 执行了 resolve,则 执行 then 的实例方法
情况2:若 Promise 实例对象 执行了 reject,则 执行 catch 的实例方法
Promise.allSellted([...个 promise 实例对象])
需求:同时请求 N 个 ajax ,当 所有的 ajax 都获取到了 数据,再进行下一步...
若用 Promise.all 会有 一个"痛点":一旦有 一个 ajax 报错,则所有的 ajax 数据都拿不到...
那么,上述问题的 解决办法:
Promise.allSettled([oPSuccess1, oPSuccess2, oPSuccess3, oPError1])
作用:不管 ...promise 实例对象 走不走 reject,最终当 所有的 Promise 实例对象 都 由 pending 改变了状态
则走 .then(回调函数),then 中 回调函数的参数 是 数组,数组的数据形式 为下述类型:
[
{status: 'fulfilled', value: '第1S 的 resolve 数据'},
{status: 'fulfilled', value: '第2S 的 resolve 数据'},
{status: 'fulfilled', value: '第3S 的 resolve 数据'},
{status: 'rejected', reason: '第0.4S 的 reject 数据'}
]
Promise.any([...个 promise 实例对象])
Promise的特点:
- 对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”;
- 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
Promise的缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
总结:
**Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
****状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
**注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的
import()异步导入
其作用:将 import关键字 的导入(本质是 同步的导入),变成 异步导入
好处:避免了若同步导入大量的数据,而造成 阻塞主线程的代码执行。
Promise解决了回调地狱的问题
async/await
async 函数中,可以 将 Promise 实例对象 的链式写法,变成 同步写法
async/await 是 ECMAScript 2017(ES8)引入的语言特性,用于异步编程。async/await 的目的是使异步代码的编写和阅读更加简单和直观,类似于同步代码的写法。
async/await 实际上是基于 Promise 的,它通过 async 和 await 关键字来定义异步函数和处理异步操作的结果。
async 关键字用于定义一个异步函数,异步函数会返回一个 Promise 对象.异步函数内部可以使用await 关键字来等待一个 Promise 对象的完成,并暂停异步函数的执行,直到 Promise 完成或拒绝,并返回它的结果。当 Promise完成或拒绝时,异步函数会继续执行。
async/await其实是Generator 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中.