1. promise是什么?

promise - 图1

1.1 概念

抽象表达

  • Promise是一门新的技术(ES6规范)
  • Promise是JS中进行异步编程新解决方案(旧方案使用普通的回调函数)

具体表达

  • 从语法上来说, Promise是一个构造函数,可以进行对象实例化;
  • 从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值

【异步操作编程包含: fs文件操作、数据库操作、定时器】

1.2 Promise的状态改变

1.2.1 promise的状态是什么?

promise状态Promise实例对象中的一个属性PromiseState
状态的属性值包含三种:

  • pending 未决定(初始化的默认值)
  • resolved/fullfilled 成功
  • rejected 失败

说明: :::info promise状态的变化只有2种,且一个promise对象只能改变一次pending变为resolved或者pending变为rejected。 无论成功还是失败,都会有一个结果数据。 成功的结果一般称为value , 失败的结果数据一般称为reason。 :::

  1. //成功
  2. const p = new Promise((resolve, reject) => {
  3. resolve('成功')
  4. }).then(res => {
  5. console.log(res)
  6. })
  7. console.log(p)
  8. //失败
  9. const p = new Promise((resolve, reject) => {
  10. reject('失败')
  11. }).then(res => {
  12. console.log(res)
  13. })
  14. console.log(p)

image.pngimage.png

1.2.2 Promise对象的值:

实例对象中的另一个属性【PromiseResult】 保存着异步任务成功/失败的结果
谁可以修改PromiseResult的值呢:

  • resolve
  • reject

    1. const p = new Promise((resolve, reject) => {
    2. reject('失败')
    3. }).then(res => {
    4. console.log(res)
    5. })
    6. console.log(p)

    image.png

    1.3 Promise的基本流程

    promise - 图5

    1.4 Promise的优势

  • 指定回调函数的方式更加灵活

  • 支持链式调用,可以解决回调地狱问题

    1.4.1 什么是回调地狱?

    回调函数嵌套调用,外部函数异步执行结果是嵌套的回调执行的条件。

    1. ajax1(url, () => {
    2. ajax2(url, () => {
    3. ajax3(url, () => {
    4. doSomething()
    5. })
    6. })
    7. })

    1.4.2 回调地狱的缺点?

  • 不便于阅读

  • 不便于异常处理

    二、如何使用Promise?

    2.1 API

    2.1.1 Promise构造函数:Promise(excutor){}

  • executor函数: 执行器【Promise对象的参数】(resolve, reject) => {}

  • resolve函数: 内部定义成功时我们调用的函数 value => {}
  • reject函数: 内部定义失败时我们调用的函数 reason =>{}
  • 说明: exector会在Promise内部立即同步调用,异步操作在执行器中执行。

    1. const p = new Promise((resolve, reject) => {
    2. console.log(123)
    3. })
    4. console.log(456) //先打印123 后打印456 复制代码

    2.1.2 Promise.prototype.then方法:(onResolved, onRejected) => {}

  • onResolved函数:成功的回调函数(value) => {}

  • onRejected函数:失败的回调函数 (reson) => {}
  • 说明: 指定用于得到成功value的成功回调和用于得到失败reson的失败回调返回一个新的promise对象。

    1. const p = new Promise((resolve, reject) => {
    2. reject('失败')
    3. }).then(onResolved =>{
    4. console.log('success')
    5. }, onRejected => {
    6. console.log('error')
    7. })
    8. console.log(p)

    2.1.3 Promise.prototype.catch方法:(OnRejected)=>{}

  • onRejected函数:失败的回调函数(reson)=>{} ```javascript const p = new Promise((resolve, reject) => {
    reject(‘返回错误’) }) p.catch(reason =>{
    console.log(reason) })

const p = new Promise((resolve, reject) => { resolve(‘haha’) }).then(onResolved =>{ console.log(‘success’) }, onRejected => { console.log(‘error’) }) p.catch(error => console.log(error))

  1. <a name="PM3O7"></a>
  2. ### 2.1.4 Promise.prototype.finnaly方法:(finnaly) =>{}
  3. **finally()**方法用于指定不管 Promise 对象最后状态如何,都会执行的操作.
  4. ```javascript
  5. promise .then(result => {···})
  6. .catch(error => {···})
  7. .finally(() => {···});

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
finally本质上是then方法的特例.

2.1.5 Promise.resolve方法:(value)=> {}

  • 属于Promise函数对象并不属于实例对象
  • 作用: 为了快速得到一个Promise对象,而且可以封装一个值,将这个值转化为Promise对象;
  • value: 成功的数据或者promise对象
  • 说明:返回一个成功的promise对象
  • 如果传入的对象为非Promise类型的对象,则返回的结果为成功promise对象
  • 如果传入的参数为Promise对象,则参数的结果决定了resolve的结果

传入非promise类型对象

  1. const p1 = Promise.resolve(521)
  2. console.log(p1)

promise - 图6
传入一个promise对象

  1. const p1 = Promise.resolve(new Promise((resolve, rejcte) => {
  2. rejcte('Error')
  3. }))
  4. console.log(p1)
  5. const p2 = Promise.resolve( new Promise((resolve, reject) => {
  6. resolve('成功')
  7. }))
  8. console.log(p2)

image.pngimage.png

  1. const p2 = Promise.reject( new Promise((resolve, reject) => {
  2. resolve('成功')
  3. }))
  4. console.log(p2)
  5. const p3 = Promise.reject( new Promise((resolve, reject) => {
  6. reject('error')
  7. }))
  8. console.log(p3)

image.pngimage.png

2.1.6 Promise.reject方法:(reason) => {}

  • reason: 失败的原因
  • 作用: 快速返回一个失败的Promise对象
  • 说明: 返回一个失败的promise对象 ```javascript const p2 = Promise.reject(521); console.log(p2)
  1. ![](https://cdn.nlark.com/yuque/0/2022/webp/27402074/1657629551867-af2c9b75-4f47-498e-93b2-25d366d206d3.webp#clientId=u64295ee8-1f03-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf40e1e2e&margin=%5Bobject%20Object%5D&originHeight=171&originWidth=673&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf735af82-020d-482a-9bc0-25e97c89405&title=)
  2. ```javascript
  3. const p1 = Promise.reject(new Promise((resolve, rejcte) => {
  4. resolve('hahha')
  5. }))
  6. console.log(p1)

promise - 图11

2.1.7 Promise.all方法: (promise) =>{}

  • promise:包含N个promise的数组
  • 说明: 返回一个新的Promise,只有所有的promise都成功才执行,只要有一个失败了就直接失败。

    1. const p1 = new Promise((resolve, reject) => {
    2. resolve('OK')
    3. })
    4. const p2 = Promise.resolve('SUCCESS');
    5. const p3 = Promise.resolve('Oh Yeah');
    6. const res = Promise.all([p1,p2,p3]);
    7. console.log(res)

    promise - 图12
    如果有一个失败了,那么返回一个失败的promise对象,失败的结果值就是失败的那个promise的失败结果值。

    1. const p1 = new Promise((resolve, reject) => {
    2. resolve('OK')
    3. })
    4. const p2 = Promise.reject('Error');
    5. const p3 = Promise.resolve('Oh Yeah');
    6. const res = Promise.all([p1,p2,p3]);
    7. console.log(res)

    promise - 图13

    2.1.8 Promise.race方法:(promises) =>{}

  • promises:包含n个promise的数组

  • 谁先改变状态,谁先决定race返回的promise状态
  • 说明: 返回N个Promise的数组,第一个完成的promise的结果状态就是最终的结果状态。

    1. const p1 = new Promise((resolve, reject) => {
    2. resolve('OK')
    3. })
    4. const p2 = Promise.reject('Error');
    5. const p3 = Promise.resolve('Oh Yeah');
    6. const res = Promise.race([p1,p2,p3]);
    7. console.log(res)

    promise - 图14

    2.2 Promise的几个关键问题

    2.2.1 如何改变Promise的状态?

  • resolve(value):如果当前时pending就会变成resolved;

  • rejected: 如果当前时pending就会变成rejected;
  • 抛出异常: 如果当前状态时pending就会变成rejected;

    1. const p = new Promise((resolve,reject) => {
    2. //1. resolve函数
    3. // resolve('haha')
    4. pending => fulfilled(resolved)
    5. //2. reject函数
    6. // reject('Error') pending => rejected
    7. //3. 抛出错误
    8. throw '报错了'
    9. // pending => rejected
    10. })
    11. console.log(p)

    2.2.2 一个promise指定多个成功或者失败回调函数,都会调用吗?

    当promise改变为对应状态时都会调用,如果状态不改变都不执行。

    1. const p = new Promise((resolve,reject) => {
    2. resolve('我来了')
    3. })
    4. p.then(value => {
    5. console.log(value+ '第一次调用')
    6. })
    7. p.then(value => {
    8. console.log(value+ '第二次调用')
    9. })

    2.2.3 改变promise状态和指定回调函数谁先谁后?(resolve()和then()哪个先执行)

    下面以resolve()改变状态和then()回调函数为例:
    执行器函数是同步时:先改变状态【resolve()】,再执行回调函数【then()】

    1. const p = new Promise((resolve,reject) => {
    2. resolve('我来了')
    3. })
    4. p.then(value => {
    5. console.log(value)
    6. })

    执行器函数是异步时:先执行回调函数【then()】,再改变状态【resolve()】

    1. const p2 = new Promise((resolve,reject) => {
    2. setTimeout(() => {
    3. resolve('我来了')
    4. }, 1000);
    5. })
    6. p2.then(value => {
    7. console.log(value)
    8. })

    如何先改状态再指定回调?

  • 在执行器中直接调用resolve()/reject()

  • 延迟更长时间再调用用then()

    1. const p2 = new Promise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve('我来了')
    4. }, 1000);
    5. })
    6. setTimeout(() => {
    7. p2.then(value => {
    8. console.log(value)
    9. })
    10. }, 2000);

    什么时候才能得到数据?

  • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据

  • 如果先改变状态, 那当值给回调时,回调函数就会调用,得到数据

    2.2.4 promise.then()返回新的promise的结果状态由什么决定?

    then()指定的回调函数执行的结果决定

  • 如果抛出异常,新promise变为rejected, reason为抛出的异常;

  • 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值;
  • 如果返回的是另一个新promise,此promise的结果就会为新的promise的结果;

    1. const p1 = new Promise((resolve, reject) => {
    2. resolve('OK')
    3. })
    4. const res = p1.then( value => {
    5. // 1. 抛出错误
    6. // throw 'Error'
    7. //2. 返回的结果时非promise类型的对象
    8. // return 521
    9. //3. 返回的结果是一个promise对象,return 的结果就决定了then()的状态
    10. return new Promise((resolve, reject) => {
    11. resolve('success')
    12. })
    13. })
    14. console.log(res)

    2.2.5 promise如何串连多个操作任务?

  • promise的then()返回一个新的promise,可以看成then()的链式调用;

  • 通过then的链式调用串连多个异步/同步任务

    1. const p1 = new Promise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve('OK')
    4. }, 1000);
    5. })
    6. p1.then(value => {
    7. return new Promise((resolve, reject) => {
    8. resolve('Oh Yeah')
    9. })
    10. }).then(value => {
    11. console.log(value)
    12. })

    2.2.6 promise异常传透

  • 当使用promise的then链式调用时,可以在最后指定失败的回调;

  • 前面任何操作除了异常,都会传到最后失败的回调中处理;

    1. const p1 = new Promise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve('OK')
    4. }, 1000); })
    5. p1.then(value => {
    6. return new Promise((resolve, reject) => {
    7. reject('报错了')
    8. })
    9. }).then(value => {
    10. console.log(11)
    11. }).then(value => {
    12. console.log(22)
    13. }).catch(reason => {
    14. console.log(reason)
    15. })

    前面的任何环节,只有有失败的promise返回,或者抛出异常,都会在最后一个catch()方法对失败进行处理。

    2.2.7 中断promise链?

    promise链: 就是用.then().then().then()形成的链式promise;

  • 当使用promise的then链式调用时,在中间中断,不在调用后面的回调函数

  • 办法: 在回调函数中返回一个pending状态的promise对象 后面的then()方法都不会执行,因为promise的状态没有改变

    1. const p1 = new Promise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve('OK')
    4. }, 1000); })
    5. p1.then(value => {
    6. return new Promise((resolve, reject) => {
    7. console.log(11)
    8. })
    9. }).then(value => {
    10. //有且只有一个方法
    11. //后面的then()方法都不会执行,因为promise的状态没有改变
    12. return new Promise(() => {}) }).then(value => {
    13. console.log(22)
    14. }).catch(reason => {
    15. console.log(reason)
    16. }) // 11

    三、 async与await

    3.1 async

    async 函数是什么?

  • 它就是 Generator 函数的语法糖

前文有一个 Generator 函数,依次读取两个文件。

  1. var fs = require('fs');
  2. var readFile = function (fileName){
  3. return new Promise(function (resolve, reject){
  4. fs.readFile(fileName, function(error, data){
  5. if (error) reject(error);
  6. resolve(data);
  7. });
  8. });
  9. };
  10. var gen = function* (){
  11. var f1 = yield readFile('/etc/fstab');
  12. var f2 = yield readFile('/etc/shells');
  13. console.log(f1.toString());
  14. console.log(f2.toString());
  15. };

写成 async 函数,就是下面这样:

  1. var asyncReadFile = async function (){
  2. var f1 = await readFile('/etc/fstab');
  3. var f2 = await readFile('/etc/shells');
  4. console.log(f1.toString());
  5. console.log(f2.toString()); };

一比较就会发现,async函数就是将Generator函数的星号(*) 换成async,将yield替换成await,仅此而已。

  • 函数的返回值是promise对象 如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。 promsie对象的结果由 async函数执行的返回值决定

    1. async function foo(){
    2. // return 5
    3. // 上面写法等价于下面的写法
    4. return Promise.resolve(5)
    5. }
    6. console.log(foo())

    promise - 图15

    3.2 await

    await操作符用于等待一个Promise对象,它只能在异步函数async function中使用。 await表达式会暂停当前async function的执行,等待Promise处理完成。

  • 若Promise正常处理(fullfilled),其回调的resolve函数参数作为await表达式的值,继续执行async function;

  • 若Promise处理异常rejected,await表达式会把Promise的异常原因抛出;
  • 如果await操作符后的表达式的值不是一个Promise,则返回该值本身;
  • await右侧的表达式一般为promise对象,但是也可以是其他值; promise - 图16

    3.3 使用Async与Await的优点?

    async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

  • Async和Await关键字让我们可以用更简洁的方式写出基于Promise的异步执行行为,而无需刻意地链式调用Promise.

  • 用同步的编写方式写异步代码
  • 更方便调试代码
  • 解决回调地狱

一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。

3.4 使用Async与Await的注意事项

  1. await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

    1. async function myFunction() {
    2. try {
    3. await somethingThatReturnsAPromise();
    4. } catch (err) {
    5. console.log(err);
    6. }
    7. }
    8. // 另一种写法
    9. async function myFunction() {
    10. await somethingThatReturnsAPromise() .catch(function (err) {
    11. console.log(err);
    12. });
    13. }
  2. 多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发,这样就会缩短程序的执行时间。

    1. // 写法一
    2. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    3. // 写法二
    4. let fooPromise = getFoo();
    5. let barPromise = getBar();
    6. let foo = await fooPromise;
    7. let bar = await barPromise;
  3. await命令只能用在async函数之中,如果用在普通函数,就会报错。

    1. async function dbFuc(db) {
    2. let docs = [{}, {}, {}];
    3. // 报错
    4. docs.forEach(function (doc) {
    5. await db.post(doc);
    6. }); }
    7. //上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。
    8. async function dbFuc(db) {
    9. let docs = [{}, {}, {}];
    10. for (let doc of docs) {
    11. await db.post(doc);
    12. }
    13. }

    3.5 举例

    1. function sendAjax(method, url) {
    2. return new Promise((resolve, reject) => {
    3. const xhr = new XMLHttpRequest();
    4. // xhr.responseType = 'json';
    5. xhr.open(method, url);
    6. xhr.send();
    7. xhr.onreadystatechange = function () {
    8. if (xhr.readyState === 4) {
    9. if (xhr.status >= 200 && xhr.status < 300) {
    10. resolve(xhr.response)
    11. } else {
    12. reject(xhr.code)
    13. }
    14. }
    15. }
    16. })
    17. }
    18. //promise的then()调用
    19. sendAjax("GET", './data/content.txt').then(data => {
    20. console.log(data)
    21. }, code => {
    22. console.log(code)
    23. })
    24. // async await async
    25. function getData(){
    26. try {
    27. const data = await sendAjax("GET", './data/content.txt');
    28. console.log(data)
    29. }
    30. catch (error) {
    31. console.log(error)
    32. }
    33. }

    四、JS异步之微队列与宏队列

    4.1 原理图

    promise - 图17 一开始整个脚本作为一个宏任务执行 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列 当前宏任务执行完出队,检查微任务列表,有则一次执行,直到全部执行完 执行浏览器UI线程的渲染工作

    5.2 举例说明

    1. setTimeout(() => {
    2. console.log('timeout callback1()')
    3. Promise.resolve(3).then(
    4. value => {
    5. console.log('Promise3' +'***'+ value)
    6. }
    7. )
    8. }, 0);
    9. setTimeout(() => {
    10. console.log('timeout callback2()')
    11. }, 0);
    12. Promise.resolve(1).then(
    13. value => {
    14. console.log('Promise1' +'***'+ value)
    15. }
    16. )
    17. Promise.resolve(2).then(
    18. value => {
    19. console.log('Promise2' +'****'+ value)
    20. }
    21. )

    promise - 图18 JS中用来存储待执行回调函数的队列包含2个不同特定的队列。

    5.2 总结

    宏队列: 用来保存待执行的宏任务(回调),比如: 定时器回调/DOM事件回调/Ajax回调
    微队列: 用来保存待执行的微任务(回调),比如: promise的回调/MutationObserver的回调 :::info 宏任务:

  • 异步Ajax请求
  • setTimeout 、setInterval
  • 文件操作
  • 其他宏任务

微任务:

  • Promise.then、Promise.catch和Promise.finally
  • process.nextTick
  • 其他微任务 ::: 宏任务和微任务的执行顺序:
    image.png

  • JS引擎必须执行所有的初始化同步代码

  • 每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有的微任务之后,再执行下一个宏任务。
    宏任务和微任务交替执行。

    5.3 Promise相关面试题:

    面试题一:

    1. setTimeout(() => {
    2. console.log(1)
    3. }, 0);
    4. Promise.resolve().then(() => {
    5. console.log(2)
    6. })
    7. Promise.resolve().then(() => {
    8. console.log(4)
    9. })
    10. console.log(3)
    输出结果:// 3 2 4 1

    面试题二:

    1. setTimeout(() => {
    2. console.log(1)
    3. }, 0);
    4. new Promise((resolve) => {
    5. console.log(2)
    6. // 同步
    7. resolve() }).then(() => {
    8. console.log(3)
    9. }).then(() => {
    10. console.log(4)
    11. })
    12. console.log(5)
    输出结果://2 5 3 4 1

    面试题三

    1. const first = () => (new Promise((resolve, reject) => {
    2. console.log(3)
    3. let p = new Promise((resolve, reject) => {
    4. console.log(7)
    5. setTimeout(() => {
    6. console.log(6)
    7. resolve(6)
    8. }, 0);
    9. resolve(1)
    10. })
    11. resolve(2)
    12. p.then(arg => {
    13. console.log(arg)
    14. })
    15. }))
    16. first().then(arg => {
    17. console.log(arg) })
    18. console.log(4)
    输出结果:// 3 7 4 1 2 6
    解析: 先执行first()里面的同步,然后打印4,然后执行first里面的微任务,最后是宏任务。 promise - 图20

    面试题四

    1. setTimeout(() => {
    2. console.log(0) }, 0);
    3. new Promise((resolve, reject) => {
    4. console.log(1)
    5. resolve()
    6. }).then(() => {
    7. // 1
    8. console.log(2)
    9. new Promise((resolve, reject) => {
    10. console.log(3)
    11. resolve()
    12. }).then(() => {
    13. //3
    14. console.log(4)
    15. }).then(() => { //4
    16. console.log(5)
    17. })
    18. }).then(() => {
    19. // 2
    20. console.log(6)
    21. })
    22. new Promise((resolve, reject) => {
    23. console.log(7)
    24. resolve()
    25. }).then(() => {
    26. console.log(8)
    27. //1 })

    面试题五

    ```javascript console.log(‘1’);

setTimeout(function() { console.log(‘2’); new Promise(function(resolve) { console.log(‘3’); resolve() }).then(function() { console.log(‘4’); }) })

new Promise(function(resolve) { console.log(‘5’); resolve() }).then(function() { console.log(‘6’); })

setTimeout(function() { console.log(‘7’); new Promise(function(resolve) { console.log(‘8’); resolve() }).then(function() { console.log(‘9’); }) })

``` 正确输出顺序: 1 5 6 2 3 4 7 8 9