一、准备


1.函数对象和实例对象

函数对象:将函数作为对象使用时, 简称为函数对象。
实例对象:new 函数产生的对象, 简称为对象。

2.回调函数

同步回调函数:立即执行,完全执行完了才会结束,不会放入回调队列中。例如 数组遍历相关的回调函数 / Promise的excutor函数。
异步回调函数:不会立即执行, 会放入回调队列中将来执行。例如 定时器回调 / ajax回调 / Promise的成功|失败的回调。

//1.同步回调函数
    const arr = [1,3,5];
    arr.forEach(item => {
      //遍历回调
      console.log(item);//同步回调函数
    });
    console.log('forEach()之后');//??谁先执行,上面的回调还是下面的输出

//2.异步回调函数
    setTimeout(() => {
      console.log("setTimeout-callback");//异步回调会放入队列中来执行
    }, 0);
    console.log('setTimeout-after');

3.JS中的错误(Error)和错误处理

JS中错误的类型:

  • Error: 所有错误的父类型
  • ReferenceError: 引用的变量不存在
  • TypeError: 数据类型不正确的错误
  • RangeError: 数据值不在其所允许的范围内
  • SyntaxError: 语法错误

错误的处理:

  • 捕获错误:try...catch
  • 抛出错误: throw error

错误对象:一般有两个基本属性,分别是message(错误的相关信息),stack(函数调用栈记录信息)。

二、Promise的理解和使用


1.Promise是什么

抽象表达:Promise是JS中进行异步编程的新的解决方案。

具体表达:语法上来讲其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。功能上来说,promise对象用来封装一个异步操作并可以获取其结果。

特点:Promise的状态改变(只有2种, 只能改变一次)

  • pending变为resolved
  • pending变为rejected

说明:只有这两种,且一个promise对象只能改变一次,无论是成功还是失败,都会有一个数据结果。成功的结果数据一般称为value,失败的一般称之为reason。

Promise的基本流程:
Promise的基本流程.png

基本语法如下:

//1.创建一个新的promise对象
    const p = new Promise((resolve,reject) => {
      //2.执行异步操作任务
        setTimeout(() => {
          const time = Date.now();
          if(time%2 == 0){
            resolve('成功的数据,time= '+time);
          }
          else{
            reject('失败的数据,time= '+time);
          }
        }, 1000);
      //3.1 如果执行成功了,调用resolve(value)
      //3.2 如果执行失败了,调用reject(reason)
    })
    p.then(
      value => {
        //接受得到成功的value   onResolved
        console.log('成功的回调', value);
      },
      reason => {
        //接受得到失败的reason  onRejected
        console.log('失败的的回调', reason);
      }
    )

2.为什么使用Promise

原因如下:

  • 指定回调函数的方式更加灵活
    旧方法:必须在启动异步任务前指定。promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
  • 支持链式调用, 可以解决回调地狱问题
    什么是回调地狱---回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件。回调地狱的缺点---不便于阅读 / 不便于异常处理。解决方案---promise链式调用。终极解决方案---async/await(之后会说到,这和Promise本质上一样)。

3.具体如何使用Promise

主要API:

        Promise构造函数: Promise (excutor) {}
        Promise.prototype.then方法: (onResolved, onRejected) => {}
        Promise.prototype.catch方法: (onRejected) => {}
        Promise.resolve方法: (value) => {}
        Promise.reject方法: (reason) => {}
        Promise.all方法: (promises) => {}
        Promise.race方法: (promises) => {}

代码如下:

/*
   1. Promise构造函数: Promise (excutor) {}
        excutor函数: 同步执行  (resolve, reject) => {}
        resolve函数: 内部定义成功时我们调用的函数 value => {}
        reject函数: 内部定义失败时我们调用的函数 reason => {}
        说明: excutor会在Promise内部立即同步回调,异步操作在执行器中执行

    2. Promise.prototype.then方法: (onResolved, onRejected) => {}
        onResolved函数: 成功的回调函数  (value) => {}
        onRejected函数: 失败的回调函数 (reason) => {}
        说明: 指定用于得到成功value的成功回调和用于得到失败reason的失败回调
              返回一个新的promise对象

    3. Promise.prototype.catch方法: (onRejected) => {}
        onRejected函数: 失败的回调函数 (reason) => {}
        说明: then()的语法糖, 相当于: then(undefined, onRejected)

    4. Promise.resolve方法: (value) => {}
        value: 成功的数据或promise对象
        说明: 返回一个成功/失败的promise对象

    5. Promise.reject方法: (reason) => {}
        reason: 失败的原因
        说明: 返回一个失败的promise对象

    6. Promise.all方法: (promises) => {}
        promises: 包含n个promise的数组
        说明: 返回一个新的promise, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败
    7. Promise.race方法: (promises) => {}
        promises: 包含n个promise的数组
        说明: 返回一个新的promise, 第一个完成的promise的结果状态就是最终的结果状态
  */
  new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve("成功!");
    }, 1000);
  }).then(
    value => {
      console.log("onResolve:"+value);
    }
  ).catch(reason => {
    console.log("onReject:"+reason);
  });

  //产生一个成功值为1的promise对象
  const p1 = new Promise((resolve,reject) => {
    resolve("p"+1);
  })
  const p2 = Promise.resolve("p"+2);
  const p3 = Promise.reject("p"+3);
  p1.then(value => {
    console.log(value);
  });
  p2.then(value => {
    console.log(value);
  });
  p3.catch(reason => {
    console.log(reason);
  });

 const pAll = Promise.all([p1,p2]);//只有都成功了才去处理,一旦其中一个失败就整体失败
 pAll.then(
   values => {
     console.log("all onResolve()--"+values);
   },
   reason => {
     console.log("all onReject(),error--p"+reason);
   }      
 );

const pRace =Promise.race([p1,p2,p3])//看第一个是否成功
pRace.then(
  value => {
    console.log(value);
  },
  reason => {
    console.log(reason);
  }
)

4.关于Promise的几个问题

如何改变Promise的状态?

  • resolve(value): 如果当前是pendding就会变为resolved
  • reject(reason): 如果当前是pendding就会变为rejected
  • 抛出异常: 如果当前是pendding就会变为rejected(这也是一种方式)

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

  • 当promise改变为对应状态时都会调用

代码如下:

  const p = new Promise((resolve,reject) => {
      resolve(1);//promise变为resolved状态
      reject(2);//promise变为rejected状态
      throw new Error('出错了!');//promise也变为rejected状态
      throw 3;//抛出异常,promise变为rejected状态,reason为 3
    });

    p.then(
      value => {
        console.log("1:"+value)
      },
      reason => {
        console.log('reason1:'+reason);
    });
    p.then(
        value => {
          console.log("2:"+value)
        },
        reason => {
          console.log('reason2:' + reason);
        });

改变promise状态和指定回调函数谁先谁后?

  • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调

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

  • 在执行器中直接调用resolve()/reject()
  • 延迟更长时间才调用then()

那什么时候才能得到数据?

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

代码如下:

//1.第一种执行顺序(常规)
   new Promise((resolve,reject) => {
    setTimeout(() => {
       resolve(1);//后改变状态(同时指定状态),异步执行之前保存的回调函数
       reject(2);
     }, 1000);
   }).then(//先指定回调函数,保存当前指定回调函数
     value =>{
       console.log("成功"+value);
     },
     reason => {
       console.log("失败"+reason);
     }
   )
//2.第二种执行顺序
   new Promise((resolve, reject) => {
     resolve(1);//先改状态(同时指定状态)
     reject(2);
    }).then(//后指定回调函数,并异步调用回调函数
      value => {
        console.log("成功" + value);
      },
      reason => {
        console.log("失败" + reason);
      }
    )

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

  • 简单表达: 由then()指定的回调函数执行的结果决定
  • 详细表达:

    1. 如果抛出异常, 新promise变为rejected, reason为抛出的异常.
    2. 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值(注意如果返回的是个未定义的变量什么的,也就是异常。也会变为rejected。reason为错误原因).
    3. 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果.

代码如下:

 new Promise((resolve, reject) => {
      resolve(1);
    }).then(
      value => {
        console.log("成功1 " + value);
        //return Promise.resolve(3);
        //return Promise.reject(4);
         return ;
      },
      reason => {
        console.log("失败1 " + reason);
      }
    ).then(
     value => {
       console.log("成功2 " + value);
     },
     reason => {
       console.log("失败2 " + reason);
     }
   )

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

  • promise的then()返回一个新的promise, 可以开成then()的链式调用
  • 通过then的链式调用串连多个同步/异步任务

代码如下:

    new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("执行任务1(异步)")
        resolve(1)
      }, 1000);
    }).then(
      value => {
        console.log('任务1的结果: ', value)
        console.log('执行任务2(同步)')
        return 2
      }
    ).then(
      value => {
        console.log('任务2的结果:', value)

        return new Promise((resolve, reject) => {
          // 启动任务3(异步)
          setTimeout(() => {
            console.log('执行任务3(异步))')
            resolve(3)
          }, 1000);
        })
      }
    ).then(
      value => {
        console.log('任务3的结果: ', value)
      }
    )

promise异常传透?

  • 当使用promise的then链式调用时, 可以在最后指定失败的回调
  • 前面任何操作出了异常, 都会传到最后失败的回调中处理

中断promise链?

  • 当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数
  • 办法: 在回调函数中返回一个pendding状态的promise对象

代码如下:

new Promise((resolve, reject) => {
      // resolve(1)
      reject(1)
    }).then(
      value => {
        console.log('onResolved1()', value)
        return 2
      },
      // reason => {throw reason},这里就相当于执行力这个样的代码保证异常穿透
    ).then(
      value => {
        console.log('onResolved2()', value)
        return 3
      },
      reason => {
        throw reason
      }
    ).then(
      value => {
        console.log('onResolved3()', value)
      },
      reason => Promise.reject(reason)
    ).catch(reason => {
      console.log('onReejected1()', reason)
      // throw reason
      // return Promise.reject(reason)
      return new Promise(() => { }) // 返回一个pending的promise  中断promise链
    }).then(
      value => {
        console.log('onResolved3()', value)
      },
      reason => {
        console.log('onReejected2()', reason)
      }
    )

5.自定义Promise
详细代码见github

三、asyn/await的语法和使用


1. async 函数

  • 函数的返回值为promise对象
  • promise对象的结果由async函数执行的返回值决定

2. await 表达式

  • await右侧的表达式一般为promise对象, 但也可以是其它的值
  • 如果表达式是promise对象, await返回的是promise成功的值
  • 如果表达式是其它值, 直接将此值(表达式本身 )作为await的返回值

    await必须写在async函数中, 但async函数中可以没有await。如果await的promise失败了, 就会抛出异常, 需要通过try...catch来捕获处理

代码如下:

function fn2() {
    return Promise.reject(2)
}

async function test() {
    /* let result = fn2().then(value => {
             console.log('value',value)
         },
         reason => {
             console.log('reason',reason)
         });*/
    try {
        let result = await fn2();
        console.log('result', result)
    } catch (error) {
        console.log('error', error)
    }
}

test()

四、宏队列与微队列


具体示意图:
宏队列与微队列.png

理解

  1. 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM事件回调/ajax回调
  2. 微列队: 用来保存待执行的微任务(回调), 比如: promise的回调/MutationObserver的回调
  3. JS执行时会区别这2个队列。JS引擎首先必须先执行所有的初始化同步任务代码。每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行。

代码如下:

setTimeout(() => {
    console.log('setTimeout1')
    Promise.resolve(3).then(value => {
        console.log('onResolved3', value)
    })
})

setTimeout(() => {
    console.log('setTimeout2')
})

Promise.resolve(1).then(value => {
    console.log('onResolved1', value)
})

Promise.resolve(2).then(value => {
    console.log('onResolved2', value)
})

/*
onResolved1 1
onResolved2 2
setTimeout1
onResolved3 3
setTimeout2*/

//node上执行结果(之后学习了node再来深入探讨)

/*
onResolved1 1
onResolved2 2
setTimeout1
setTimeout2
onResolved3 3*/

这里看出微队列的优先级更高。

下面探讨几个面试题

//题一
 setTimeout(() => {
      console.log(1)
    }, 0);
    Promise.resolve().then(() => {
      console.log(2);
    });
    Promise.resolve().then(() => {
      console.log(4);
    });
    console.log(3)
//浏览器输出顺序:3 2 4 1  

//题二
 setTimeout(() => {
      console.log(1);
    }, 0);
    new Promise((resolve) => {
      console.log(2);
      resolve();
    }).then(() => {
      console.log(3);
    }).then(() => {
      console.log(4);
    });
    console.log(5);
//浏览器输出顺序:2 5 3 4 1

//题三
const first = () => (new Promise((resolve, reject) => {
    console.log(3)
    let p = new Promise((resolve, reject) => {
      console.log(7)
      setTimeout(() => {
        console.log(5)
        resolve(6) //会被忽略,因为状态已经改变过了
      }, 0)
      resolve(1)
    })
    resolve(2)
    p.then((arg) => {
      console.log(arg)
    })

  }))

  first().then((arg) => {
    console.log(arg)
  })
  console.log(4)
//浏览器输出顺序:3 7 4 1 2 5

//题四
    setTimeout(() => {
        console.log("0")
    }, 0)
    new Promise((resolve, reject) => {
        console.log("1")
        resolve()
    }).then(() => {
        console.log("2")
        new Promise((resolve, reject) => {
            console.log("3")
            resolve()
        }).then(() => {
            console.log("4")
        }).then(() => {
            console.log("5")
        })
    }).then(() => {
        console.log("6")
    })

    new Promise((resolve, reject) => {
        console.log("7")
        resolve()
    }).then(() => {
        console.log("8")
    })
//浏览器输出顺序:1 7 2 3 8 4 6 5 0
/*第四题详细解释
 1.首先将‘0’放入宏队列等待微队列执行完毕后再执行,此时宏队列情况[0]
 2.遇到第一个Promise,其中的执行器为同步执行所以直接输出‘1’
 3.接下来执行resolve(),将‘2’放入微队列此时微队列情况[2],由于‘2’还没执行所以后续代码暂时不考虑
 4.此时遇到第二个Promise,同步执行‘7’,直接输出后再执行resolve(),将‘8’放入微队列等待执行,此时的微队列情况[2,8]
 5.接下来执行微队列代码,首先输出‘2’,然后遇到第三个Promise,直接输出‘3’,再执行resolve(),将‘4’放入微队列,后续代码需要等‘4’执行后才会考虑,但是该Promise已经执行完了有了结果,所以将之后的then放入微队列,即‘6’.此时微队列情况[8,4,6]
 6.继续执行微队列,首先输出‘8’,然后输出‘4’,然后将‘4’后面的then送入微队列,所以此时微队列情况[6,5]
 7.继续执行微队列,输出‘6’,然后在输出‘5’,此时微队列已经清空,所以执行宏队列输出‘0’.
 
*/
Last modification:August 11, 2020
如果觉得我的文章对你有用,请随意赞赏