回调函数

  • 概念:一个函数作为另一个函数的参数;先执行一个函数,把他的执行结果作为另一个函数的参数;

  • 弊端:会产生回调地狱。(回调函数的层层嵌套 例如:京东首页的 tab 栏,家用电器里面有电视,电视里面有各种牌子,不同牌子又有不同的配色等等,类似这样下一级的参数为上一级的返回值的)

Promise

代码可见:github

  • 概念:

    1. Promise 是异步编程的一种解决方案,比传统的回调函数和事件更合理、更强大
    2. ES6 的 Promise 是一个构造函数,用来生成 Promise 实例,Promise 实例是异步操作管理者
    3. Promise 代表了未来某个将要发生的事件 (通常是一个异步操作) 有了 Promise 对象
    4. 可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数 (回调地狱)
    5. Promise 本身还是在使用回调函数 (只不过比回调函数多了一种状态管理)
    6. promise 三个状态:
      • 初始化状态 pending
      • 成功状态 fullfilled
      • 失败状态 rejected
  • 如何使用

    1. 因为 promise 是构造方法,就是一个类,所以说使用的时候需要进行实例化

    2. 使用 new 进行实例化一个 Promise 内置有三种状态

    3. promise 内置一个参数 参数是回调函数 回调函数中有两个参数 其实都是函数,这两个参数代表着成功状态和失败状态

      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      let promise = new Promise((resolve,reject)=>{
      // 回调函数中有两个参数,这两个参数都是函数,
      console.log(typeof resolve); //function
      console.log(typeof reject); //function
      // 初始化状态其实就是许诺状态
      let num = 10;
      if(num>9){
      resolve("成功");
      }else{
      reject("失败")
      }
      })

      以上是我们创建的 promise

      但是创建之后如何使用 现在的 promise 对象和没写一样 一点作用都没有

      使用 promise 对象进行触发 promise 对象中的 then 方法

      then 中内置一个或者两个参数 第一个参数是我们定义成功状态的执行方法

      第二个参数是我们定义的失败状态的执行方法 也就是说两个参数 都是函数

      • 传统写法
      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // Promise内置一个参数  参数是回调函数  回调内置两个参数
      // 第一个参数是成功状态 第二个参数是失败状态
      // 这两个参数其实都是函数 只不过还没有定义
      let promise = new Promise((resolve,reject)=>{
      // 初始化一个值
      let num = 10;
      // 判定
      if (num > 90){
      resolve('成功');
      }else{
      reject('失败');
      }
      });
      // ps:可以理解为上面的resolve("成功")和reject("失败")为函数的调用,括号里传的为实参;而promise.then里面的两个参数为函数的声明,data和err为形参;
      promise.then(data=>{
      console.log('执行已经' + data);
      },err=>{
      console.log('执行出现了' + err);
      });
      • 链式操作 (现写法)
      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      let promise = new Promise((resolve,reject)=>{
      // 初始化一个值
      let num = 10;
      console.log(aaa);
      // 判定
      if (num > 90){
      resolve('成功');
      }else{
      reject('失败');
      }
      });
      // 我们一般不会在then方法中同时传递两个参数(传统写法),因为如果说传递两个参数 那么不会进行异常处理
      // 所以说 失败的状态 我们一般情况下 使用catch状态进行触发 这样经过了异常处理
      // 使用then方法触发成功状态 使用catch方法触发失败状态
      // 但是注意 catch不是promise的方法 而是then的返回值的方法
      // 也就是说 promise对象使用then触发之后 then方法的返回值也是一个promise
      //常规写法
      let p =promise.then(data=>{
      console.log("当前状态为:"+data)
      })
      p.catch(err=>{
      console.log("当前状态为:"+err)
      })
      // 链式操作
      promise.then(data=>{
      console.log(data);
      }).catch(err=>{
      console.log(err);
      })
      • 链式操作
      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      let promise = new Promise((resolve,reject)=>{
      if (true){
      resolve('一级栏目');
      }else{
      reject('没出来');
      }
      })

      // 第一个promise中的无论是成功状态还是失败装填的返回值
      // 都作为链式操作中第二个promise的resolve(成功)状态
      // let p = promise.then(data=>{
      // console.log(data);
      // return data; //记得return出去
      // }).catch(err=>{
      // console.log(err);
      // return err; //记得return出去
      // })

      // p.then(data1=>{
      // console.log("根据" + data1 + "获取二级栏目");
      // })

      let p =promise.then(data=>{
      console.log(data);
      return data;//记得return出去
      }).then(data1=>{
      console.log("根据"+data1+"获取二级栏目")
      return "根据"+data1+"获取二级栏目";//记得return出去
      }).then(data2=>{
      console.log(data2+"获取三级栏目")
      return data3;//记得return出去
      })
      /*
      一级栏目
      根据一级栏目获取二级栏目
      根据一级栏目获取二级栏目获取三级栏目
      */
      • Promise.all()
        1. all 方法是处理 Promise 的方法 他是 Promise 中的一个静态方法 static
        2. 也就是说 我们使用的时候 不需要实例化 我们直接使用 Promise.all 方法就可以
        3. 它主要的作用是执行所有的 promise
        4. 内置一个参数 参数是一个数组
      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      let p1 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('2s钟之后执行p1');
      },2000)
      })

      let p2 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('3s钟之后执行p2');
      },3000)
      })

      let p3 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('1s钟之后执行p3');
      },1000)
      })

      // all方法是执行所有的Promise
      // 内置一个参数 参数是一个数组
      Promise.all([p1,p2,p3]).then(data=>console.log(data));//['2s钟之后执行p1', '3s钟之后执行p2', '1s钟之后执行p3']
      • Promise.race()
        1. all 方法是处理 Promise 的方法 他是 Promise 中的一个静态方法 static
        2. 也就是说 我们使用的时候 不需要实例化 我们直接使用 Promise.race 方法就可以
        3. 他只执行最快的那个 promise
      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      let p1 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('2s钟之后执行p1');
      },2000)
      })

      let p2 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('3s钟之后执行p2');
      },3000)
      })

      let p3 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
      resolve('1s钟之后执行p3');
      },1000)
      })

      //race方法是执行最快的Promise
      Promise.race([p1,p2,p3]).then(data=>console.log(data));//1s钟之后执行p3

async

  • 概念:

    1. 真正意义上去解决异步回调的问题,同步流程表达异步操作
    2. 本质上 async 是 Promise 的语法糖
  • 特点:

    1. async 函数是一个异步函数 但是主要的作用是处理函数内部的异步问题
    2. 将函数内部的异步代码转化为同步代码 再执行
    3. 在函数中 如果说程序碰到 await 程序会陷入阻塞状态
    4. 也就是说 在 async 函数中有 await 关键字 这个关键字只能出现在 async 函数中
    5. 一直到程序执行完成 await 后面的表达式 程序才会向下进行
  • async 函数主要有三点

    1. 函数必须使用 async 进行修饰

    2. 函数的返回值是一个 promise

    3. await 将异步代码转化为同步代码

    4. await 可以直接获取 Promise 中的 resolve 状态 并且是异常处理过后的

      js
      1
      2
      3
      4
      5
      6
      7
      8
      async function fn(){
      let v1 = await "这是第一行代码";
      let v2 = await "这是第二行代码";
      console.log(v1);
      console.log(v2);
      }
      fn(); //这是第一行代码
      //这是第二行代码

      案例:

      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
         function fn1() {
      return new Promise((resolve, reject) => {
      setTimeout(() => {
      resolve('2s钟之后执行p1');
      }, 2000)
      })
      }

      function fn2() {
      return new Promise((resolve, reject) => {
      setTimeout(() => {
      resolve('3s钟之后执行p2');
      }, 3000)
      })
      }

      function fn3() {
      return new Promise((resolve, reject) => {
      setTimeout(() => {
      resolve('1s钟之后执行p3');
      }, 1000)
      })
      }
      // 定义async函数
      async function fn() {
      let v1 = await fn1();
      console.log(v1)
      let v2 = await fn2();
      console.log(v2)
      let v3 = await fn3();
      console.log(v3)
      }
      fn();
      /*
      2s钟之后执行p1
      3s钟之后执行p2
      1s钟之后执行p3
      */

      案例传参:

      js
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      const connect = require('./lib/connect');

      function fn1(){
      return new Promise((resolve,reject)=>{
      connect.query('select * from category_first',(err,data)=>{
      let result = data[1].first_id;
      resolve(result);
      })
      })
      }

      function fn2(v){
      return new Promise((resolve,reject)=>{
      connect.query('select * from category_second where first_id = ?',v,(err,data)=>{
      let result = data[0].second_id;
      resolve(result);
      })
      })
      }

      function fn3(v){
      return new Promise((resolve,reject)=>{
      connect.query('select * from category_thired where second_id = ?',v,(err,data)=>{
      let result = data[0].thired_id;
      resolve(result);
      })
      })
      }

      function fn4(v){
      return new Promise((resolve,reject)=>{
      connect.query('select goods_name,goods_id from goods_list where thired_id = ?',v,(err,data)=>{
      resolve(data);
      })
      })
      }

      async function fn(){
      let v1 = await fn1();
      let v2 = await fn2(v1);
      let v3 = await fn3(v2);
      let v4 = await fn4(v3);
      console.log(v4);
      }
      fn();
  • 后续补充

  1. async 函数返一个 promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步任务完成,再接着执行后面的语句;(为了验证,看如下代码)

    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
        async function test(){
    await setTimeout(() => {
    console.log("我是不是先执行");
    }, 1000);
    console.log("我什么时候执行")
    }
    test();
    // 我什么时候执行
    //(1s后)我是不是最开始执行

    我们发现,并非我们预想的执行顺序,先打印‘我是不是最开始执行’,而是从下面先开始执行了?其实,这里是有条件的:

    • 如果 await 后面跟的是 promise 对象,会将 promise 异步操作转为同步等待 promise 的 resolve/reject 返回结果,再接着执行函数体后面的语句

      (继续测试如下):

    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        async function test() {
    await new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log("我是不是先执行");
    }, 1000);
    })
    console.log("我什么时候执行")
    }
    test();
    // (1s后) 我是不是最开始执行

    我们发现,这跟预期的还是不一样。最下面的 console.log(‘我什么时候执行’)没执行。

    原来:

    • 如果 await 的是一个 promise 对象,那么要等待这个对象解析完成;
    • 如果没有 resolve/reject,那么后面的内容就不再执行;

    我们再修改,测试如下:

    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        async function test() {
    await new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve();
    console.log("我是不是先执行");
    }, 1000);
    })
    console.log("我什么时候执行")
    }
    test();
    // (1s后) 我是不是最开始执行
    // (1s后,和上面同时) 我什么时候执行
  • 小结论

所以我们可以得出一个结论

  1. await 后面如果不是一个promise对象,那么它就按正常的js顺序执行,先执行同步代码,当主线程空闲了,再去执行异步队列的任务;

  2. await 后面如果是promise对象那么要等待这个对象解析完成,如果没有resolve或者reject,那么后面的内容就不会执行;如果有resolve或者reject,那么后面的内容正常执行

    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    async function test() {
    console.log("async1");
    await new Promise((resolve, reject) => {
    resolve()
    });
    console.log('async2');
    };
    //等价于
    async function test() {
    console.log("async1");
    await new Promise((resolve, reject) => {
    resolve()
    }).then(() => console.log('async2'));
    };

  • await 的返回值
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    async function test() {
let result = await new Promise((resolve, reject) => {
console.log("await1")
setTimeout(() => {
resolve("pkpkq");
}, 1000);
})
console.log("await2",result);
return result;
}
test().then((data)=>{
console.log(data);
})
//await1
//(1s后)await2 pkpkq
//(1s后)pkpkq

我们发现:

  • await 的返回值就是 Promise 的 resolve/reject 返回结果,然后通过 async 函数 return 出来,相当于 Promise.resolve (“返回结果”);

参考文献

异步回调中 Async Await 和 Promise 区别