1、Promise

1.1 Promise是什么?

先来看一段代码:

  1. const https = require('https');
  2. function httpPromise(url){
  3. return new Promise(function(resolve,reject){
  4. https.get(url, (res) => {
  5. resolve(data);
  6. }).on("error", (err) => {
  7. reject(error);
  8. });
  9. })
  10. }
  11. httpPromise().then( function(data){
  12. // todo
  13. }).catch(function(error){ //todo })
  14. httpPromise(url1)
  15. .then(res => {
  16. console.log(res);
  17. return httpPromise(url2);
  18. })
  19. .then(res => {
  20. console.log(res);
  21. return httpPromise(url3);
  22. })
  23. .then(res => {
  24. console.log(res);
  25. return httpPromise(url4);
  26. })
  27. .then(res => console.log(res));。

从上面的例子可以看出,Promise 会接收一个执行器,在这个执行器里,我们需要把目标的异步任务给”填进去“。

在 Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolve 或 reject 来改变 Promise实例的状态。

1.2 Promise 实例有三种状态:

  • 等待中(pending):表示进行中。这是 Promise 实例创建后的一个初始态;
  • 完成了 (resolved):表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
  • 拒绝了(rejected):表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;

一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变。

pending->resolved 或 pending->rejected变化不可逆。

  1. // 刚定义时,状态默认为 pending
  2. const p1 = new Promise((resolve, reject) => {
  3. })
  4. // 执行 resolve() 后,状态变成 resolved
  5. const p2 = new Promise((resolve, reject) => {
  6. setTimeout(() => {
  7. resolve()
  8. })
  9. })
  10. // 执行 reject() 后,状态变成 rejected
  11. const p3 = new Promise((resolve, reject) => {
  12. setTimeout(() => {
  13. reject()
  14. })
  15. })
  16. // 直接返回一个 resolved 状态
  17. Promise.resolve(100)
  18. // 直接返回一个 rejected 状态
  19. Promise.reject('some error')
  1. new Promise((resolve, reject) => {
  2. resolve('success')
  3. // 无效
  4. reject('reject')
  5. })

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的。

  1. new Promise((resolve, reject) => {
  2. console.log('new Promise')
  3. resolve('success')
  4. })
  5. console.log('finifsh')
  6. // new Promise -> finifsh

1.3 状态和 then catch

状态变化会触发 then catch

  • pending 不会触发任何 then catch 回调
  • 状态变为 resolved 会触发后续的 then 回调
  • 状态变为 rejected 会触发后续的 catch 回调

then catch 会继续返回 Promise ,此时可能会发生状态变化!!!

  1. // then() 一般正常返回 resolved 状态的 promise
  2. Promise.resolve().then(() => {
  3. return 100
  4. })
  5. // then() 里抛出错误,会返回 rejected 状态的 promise
  6. Promise.resolve().then(() => {
  7. throw new Error('err')
  8. })
  9. // catch() 不抛出错误,会返回 resolved 状态的 promise
  10. Promise.reject().catch(() => {
  11. console.error('catch some error')
  12. })
  13. // catch() 抛出错误,会返回 rejected 状态的 promise
  14. Promise.reject().catch(() => {
  15. console.error('catch some error')
  16. throw new Error('err')
  17. })

2、Promise常见题

2.1 Promise特点

Promise 的特点是什么,分别有什么优缺点?什么是 Promise 链?Promise 构造函数执行和 then 函数执行有什么区别?

特点:实现链式调用

优点:Promise 很好地解决了回调地狱的问题

缺点:比如无法取消 Promise,错误需要通过回调函数捕获。

Promise链:

  • 每次调用 then 之后返回的都是一个全新的Promise,因此又可以接着使用then方法,由此形成promise链
  • 在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

Promise 构造函数执行和 then 函数执行有什么区别:

  • 构造 Promise 的时候,构造函数内部的代码是立即执行的
  • then函数在promise.resolve()执行后执行

2.2 代码题

  1. // 第一题
  2. Promise.resolve().then(() => {
  3. //没报错返回resolved状态
  4. console.log(1) // 1
  5. }).catch(() => {
  6. console.log(2) // 不会走
  7. }).then(() => {
  8. console.log(3) // 3
  9. }) // resolved
  10. //结果 1 3
  11. // 第二题
  12. Promise.resolve().then(() => {
  13. console.log(1)
  14. // 返回 rejected 状态的 promise
  15. throw new Error('erro1')
  16. }).catch(() => {
  17. // 返回 resolved 状态的 promise
  18. console.log(2)
  19. }).then(() => {
  20. console.log(3)
  21. })
  22. //结果 1 2 3
  23. // 第三题
  24. Promise.resolve().then(() => { // 返回 rejected 状态的 promise
  25. console.log(1)
  26. throw new Error('erro1')
  27. }).catch(() => { // 返回 resolved 状态的 promise
  28. console.log(2)
  29. }).catch(() => {
  30. console.log(3)
  31. })
  32. //结果 1 2

3、Generator

Generator是什么:

Generator 一个有利于异步的特性是,它可以在执行中被中断、然后等待一段时间再被我们唤醒。通过这个“中断后唤醒”的机制,我们可以把 Generator看作是异步任务的容器,利用 yield 关键字,实现对异步任务的等待。

  1. function firstAjax() {
  2. ajax(url1, () => {
  3. // 调用secondAjax
  4. secondAjax()
  5. })
  6. }
  7. function secondAjax() {
  8. ajax(url2, () => {
  9. // 处理逻辑
  10. })
  11. }
  12. ajax(url, () => {
  13. // 调用firstAjax
  14. firstAjax()
  15. })

我们可以通过 Generator 函数解决回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

  1. function *fetch() {
  2. yield ajax(url, () => {})
  3. yield ajax(url1, () => {})
  4. yield ajax(url2, () => {})
  5. }
  6. let it = fetch()
  7. let result1 = it.next()
  8. let result2 = it.next()
  9. let result3 = it.next()

发现Generator确实有点麻烦,每次迭代都要.next()才能继续下一步的操作,直到done为true时停止。所以我们利用一个第三方库(co)直接执行:

co是什么:generator函数(生成器函数)的自动执行函数。

  1. const co = require('co');
  2. co(httpGenerator());

4、async/await

4.1 async/await产生背景

  • 异步回调callback
  • Promise then catch链式调用,但也是基于回调函数
  • async/await是同步语法,彻底消灭回调函数

4.2 使用

一个函数如果加上 async ,那么该函数就会返回一个 Promise

  1. async function test() {
  2. return "1"
  3. }
  4. console.log(test()) // -> Promise {<resolved>: "1"}

async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用。

用同步方法编写异步。

  1. function loadImg(src) {
  2. const promise = new Promise((resolve, reject) => {
  3. const img = document.createElement('img')
  4. img.onload = () => {
  5. resolve(img)
  6. }
  7. img.onerror = () => {
  8. reject(new Error(`图片加载失败 ${src}`))
  9. }
  10. img.src = src
  11. })
  12. return promise
  13. }
  14. async function loadImg1() {
  15. const src1 = 'http:xxx.png'
  16. const img1 = await loadImg(src1)
  17. return img1
  18. }
  19. async function loadImg2() {
  20. const src2 = 'https://xxx.png'
  21. const img2 = await loadImg(src2)
  22. return img2
  23. }
  24. (async function () {
  25. // 注意:await 必须放在 async 函数中,否则会报错
  26. try {
  27. // 加载第一张图片
  28. const img1 = await loadImg1()
  29. console.log(img1)
  30. // 加载第二张图片
  31. const img2 = await loadImg2()
  32. console.log(img2)
  33. } catch (ex) {
  34. console.error(ex)
  35. }
  36. })()

4.3 典型场景

4.3.1 并发

下面代码模拟了三个请求接口,也就是三个请求没有任何依赖关系,却要等到上一个执行完才执行下一个,带来时间上的浪费。

  1. (async () => {
  2. const getList1 = await getList1();
  3. const getList2 = await getList1();
  4. const getList3 = await getList2();
  5. })();

解决方案:

  1. (async () => {
  2. const listPromise = getList();
  3. const anotherListPromise = getAnotherList();
  4. await listPromise;
  5. await anotherListPromise;
  6. })();
  7. // 也可以使用
  8. (async () => {
  9. Promise.all([getList(), getAnotherList()]).then(...);
  10. })();

4.3.2 捕获错误

使用 try catch 捕获错误,当我们需要捕获多个错误并做不同的处理时,try catch 会导致代码杂乱:

  1. async function asyncTask(cb) {
  2. try {
  3. const res1 = await request1(resByRequest1); //resByRequest1返回值为promise
  4. if(!res1) return cb('error1');
  5. } catch(e) {
  6. return cb('error2');
  7. }
  8. try {
  9. const res2 = await request2(resByRequest2); //resByRequest2返回值为promise
  10. } catch(e) {
  11. return cb('error3');
  12. }
  13. }

简化错误捕获:添加一个中间函数:

  1. export default function to(promise) {
  2. return promise.then(data => {
  3. return [null, data];
  4. })
  5. .catch(err => [err]);
  6. }

错误捕获的代码:

  1. async function asyncTask() {
  2. let err, data
  3. [err, data1] = await to(resByRequest1);
  4. if(!data1) throw new Error('xxxx');
  5. [err, data2] = await to(resByRequest2);
  6. if(!data2) throw new Error('xxxx');
  7. }

5、async/await和Promise的关系

  • async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
  • await相当于Promise的then
  • try…catch可捕获异常,代替了Promise的catch
  1. async function fn2() {
  2. return new Promise(() => {})
  3. }
  4. console.log( fn2() )
  5. async function fn1() { // 执行async函数,返回的是一个Promise对象
  6. return 100
  7. }
  8. console.log( fn1() ) // 相当于 Promise.resolve(100)
  • await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
  • await 后续跟非 Promise 对象:会直接返回
  1. (async function () {
  2. const p1 = new Promise(() => {})
  3. await p1
  4. console.log('p1') // 不会执行
  5. })()
  6. (async function () {
  7. const p2 = Promise.resolve(100)
  8. const res = await p2
  9. console.log(res) // 100
  10. })()
  11. (async function () {
  12. const res = await 100
  13. console.log(res) // 100
  14. })()
  15. (async function () {
  16. const p3 = Promise.reject('some err')
  17. const res = await p3
  18. console.log(res) // 不会执行
  19. })()

try…catch 捕获 rejected 状态

  1. (async function () {
  2. const p4 = Promise.reject('some err')
  3. try {
  4. const res = await p4
  5. console.log(res)
  6. } catch (ex) {
  7. console.error(ex)
  8. }
  9. })()

总体来看:

  • async 封装 Promise
  • await 处理 Promise 成功
  • try…catch 处理 Promise 失败

6、异步本质

await 是同步写法,但本质还是异步调用。

  1. async function async1 () {
  2. console.log('async1 start') // 2
  3. await async2()
  4. console.log('async1 end') // 5
  5. }
  6. async function async2 () {
  7. console.log('async2') // 3
  8. }
  9. console.log('script start') // 1
  10. async1()
  11. console.log('script end') //4

7、async和await常见题

async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么?

特点:

  • 一个函数如果加上async 那么其返回值是Promise,async 就是将函数返回值使用 Promise.resolve() 进行包裹,和then处理返回值一样
  • await只能配合async使用 不能单独使用

优点:

  • 相比于Promise来说优势在于能够写出更加清晰的调用链,并且也能优雅的解决回调地狱的问题

缺点:

  • 因为await将异步代码变成了同步代码,如果多个异步之间没有关系,会导致性能降低

原理:

  • await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator