Promise与async await的实际使用场景

前言

在Web前端开发中,处理异步操作是非常常见的需求。为了解决这个问题,ES6引入了Promise,ES7新增了async await。

一、promise是干嘛的?

Promise是一个表示异步操作最终完成或失败的对象。它可以将回调地狱转化为链式调用,使代码更加整洁和可读。

Promise 有三种状态:

  • pending (等待态)
  • fulfiled (成功态)
  • rejected (失败态)
  • Promise 状态的转变是不可逆且只能发生一次。

    也就是说,一个 Promise 不能从 fulfiled 状态变回 pending 状态,也不能从 rejected 状态变为 pending 或者 fulfiled 状态。

    一旦 Promise 从 pending 状态变为 fulfiled 或 rejected ,它就永远不会再改变。

    promise是用来解决两个问题的:

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象。
  • promise可以支持多个并发的请求,获取并发请求中的数据。
  • 简单一句话就是promise是用来解决异步的。

    js的执行顺序就是从上到下,先同步后异步。

    console.log('1')
    setTimeout(()=>{
       console.log('2')
    })
    console.log('3')
    
    //打印结果顺序是132
    

    因为setTimeout是异步,所以会打印3后才打印2,如果我们想打印123,怎么办呢?那就把setTimeout变成同步就可以了,这时候就可以用到promise了。

    二、Promise 是同步还是异步?

  • Promise 本身是同步的 ,是用来管理异步编程的。
  • Promise 的回调then,catch是异步 ,解决异步回调的问题。
  • 三、Promise的应用场景

    Promise 的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:

  • resolve :异步操作执行成功后的回调函数(把状态设置为成功,执行then方法)。
  • reject:异步操作执行失败后的回调函数(把状态设置为失败,执行catch方法)。
  • 1、 then 的用法

    then() 方法返回一个 新的Promise对象,then 方法最多传入两个参数:

  • 第一个对应 resolve 的回调。
  • 第二个对应 reject 的回调。
  • let res = new Promise((resolve, reject) => {
      if (true) {
         resolve("success");
      } else {
         reject("fill");
      }
    });
    res.then((data) => {
       console.log(data); // 处理resolve回调
    }).catch((data) => {
       console.log(data); // 处理reject回调
    });
    

    then的链式调用:

    从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

    request('test1.py', data)
    	.then(function (data1) {
    	    console.log('第一次请求成功, 这是返回的数据:', data1);
    	    return request('test2.py', data1);
    	})
    	.then(function (data2) {
    	    console.log('第二次请求成功, 这是返回的数据:', data2);
    	    return request('test3.py', data2);
    	})
    	.then(function (data3) {
    	    console.log('第三次请求成功, 这是返回的数据:', data3);
    	})
    	.catch(function (error) {
    	    console.log('sorry, 请求失败了, 这是失败信息:', error);
    	});
    

    Promise对象的then方法返回一个新的Promise对象,因此可以通过链式调用then方法。

    then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。两个函数只会有一个被调用,函数的返回值将被用作创建then返回的Promise对象。

    then的两个参数的返回值可以是以下三种情况中的一种:

  • return 一个同步的值 ,或者 undefined(当没有返回一个有效值时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。
  • return 另一个 Promise,then方法将根据这个Promise的状态和值创建一个新的Promise对象返回。
  • throw 一个同步异常,then方法将返回一个rejected状态的Promise, 值是该异常。
  • var res = new Promise(function (resolve, reject) {
        resolve(1);
    });
    
    res.then(function (value) {
        console.log(value); // 1
        return value * 2; // return一个同步的值(resolved状态)
    }).then(function (value) {
        console.log(value); // 2
    }).then(function (value) {
        console.log(value); // 上面的then没有return,因此默认是return undefined
        return Promise.resolve(3);
    }).then(function (value) {
        console.log(value); // 接收到resolve(3)
        return Promise.reject(4);
    }).then(function (value) {
        console.log('resolve: ' + value);
    }, function (err) {
        console.log('reject: ' + err); // 接收到reject(4)
    })
    
    // 1
    // 2
    // undefined
    // 3
    // "reject: 4"
    

    2、catch 的用法

    用于处理Promise失败后的回调函数,也返回一个新的Promise对象。

    它主要用于捕获异步操作过程中发生的错误。其实它和 then 的第二个参数一样,用来指定 reject 的回调。 用法是这样:

    let res = new Promise((resolve, reject) => {
      if (true) {
         resolve("success");
      } else {
         reject("fill");
      }
    });
    
    p.then((data) => { console.log('resolve:' + data); })
     .catch((err) => { console.log('reject:' + err); })
    

    效果和写在then的第二个参数里面一样。

    不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。

    catch 既能处理 reject 回调,也能捕捉错误。

    3、finally 的用法

    finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作,它常常用于清理资源或执行一些不论成功失败都需要进行的操作。

    promise
        .then(res => {···})
        .catch(error => {···})
        .finally(() => {···});
    

    四、Promise 的静态方法

    1、Promise.all 的用法

    这个方法接受一个Promise对象的数组作为参数,只有当数组中的所有Promise对象都成功完成时,它才会返回一个新的成功的Promise对象,其结果是一个由每个Promise对象的返回值组成的数组,如果有任何一个Promise对象失败,all()方法将立即返回一个失败的Promise对象。

    let p1 = new Promise(function(resolve, reject){})
    let p2 = new Promise(function(resolve, reject){})
    let p3 = new Promise(function(resolve, reject){})
    
    let p = Promise.all([p1, p2, p3])
    
    p.then(() => {
      // 三个都成功,则成功    
    }, function(){
      // 只要有任何一个失败,则失败
    })
    

    Promse.all 在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个 ajax 的数据回来以后才正常显示。

    需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的。

    2、Promise.race 的用法

    Promise.race([p1, p2, p3])接受一个Promise对象的数组作为参数,哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

    与all()不同的是,race()方法会在数组中的任何一个Promise对象最先完成(无论是成功还是失败)时,就返回一个新的Promise对象,其结果就是那个最先完成的Promise对象的结果。其它的promise实例仍然会继续运行,只不过其状态和结果不会归于最终的结果。

    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      },1000)
    })
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('failed')
      }, 500)
    })
    
    Promise.race([p1, p2]).then((result) => {
      console.log(result)
    }).catch((error) => {
      console.log(error)  // 打开的是 'failed'
    })
    

    Promise.race()常用于前端发起某个异步请求获取数据,要求在规定时间内如果没拿到数据则执行另外相应的操作。

    3、Promise.allSettled 的用法

    和 Promise.all() 相似,接收一个 Promise 对象数组作为参数。

    Promise.allSettled()不会因为数组中的某个 Promise 被拒绝而立即返回,它会等待所有 Promise 都达到稳定(Settled)状态(即无论是 fulfilled 还是 rejected )后才会完成,返回一个包含每个 Promise 结果的数组。

    const p1 = Promise.resolve(3);
    const p2 = 42;   // P2 是一个非 Promise 值,它会被隐式地转换为一个已解析的 Promise。
    const p3 = new Promise((resolve, reject) => {
      setTimeout(reject, 100, 'foo');
    });
    const p4 = new Promise((resolve, reject) => {
      setTimeout(resolve, 50, 'bar');
    });
    
    const promises = [p1, p2, p3, p4];
    
    Promise.allSettled(promises).
      then((results) => results.forEach((result) => console.log(result.status)));
    
    // 输出:
    // "fulfilled"
    // "fulfilled"
    // "rejected"
    // "fulfilled"
    

    Promise.allSettled() 允许你观察一组异步操作的所有结果,无论成功与否,这对于获取并处理所有任务的最终状态非常有用。

    Promise.all() 则更关注所有 Promise 是否都成功完成,它适用于需要所有任务成功完成才能继续下一步场景。

    4、Promise.resolve 的用法

    Promise.resolve(…) 可以接收一个 值 或者是一个 Promise对象 作为参数。

    var p1 = Promise.resolve(1);
    var p2 = Promise.resolve(p1);
    var p3 = new Promise(function(resolve, reject){
      resolve(1);
    });
    var p4 = new Promise(function(resolve, reject){
      resolve(p1);
    });
    
    console.log(p1 === p2); 
    console.log(p1 === p3);
    console.log(p1 === p4);
    console.log(p3 === p4);
    
    p4.then(function(value){
      console.log('p4=' + value);
    });
    
    p2.then(function(value){
      console.log('p2=' + value);
    })
    
    p1.then(function(value){
      console.log('p1=' + value);
    })
    
    // true
    // false
    // false
    // false
    // p2=1
    // p1=1
    // p4=1
    

    当参数是普通值时,它返回一个resolved状态的Promise对象,对象的值就是这个参数;

    当参数是一个Promise对象时,它直接返回这个Promise参数。因此,p1 === p2。

    但通过new的方式创建的Promise对象都是一个新的对象,因此后面的三个比较结果都是false。

    另外,为什么p4的then最先调用,但在控制台上是最后输出结果的呢?因为p4的resolve中接收的参数是一个Promise对象p1,resolve会对p1”拆箱“,获取p1的状态和值,但这个过程是异步的。

    5、Promise.reject 的用法

    用于创建一个已拒绝的 Promise,接收一个原因(reason)作为参数,并返回一个已拒绝为该原因的 Promise 对象。

    const promise = Promise.reject('error');
    
    promise.catch((reason) => {
      console.log(reason); // 输出 "error"
    });
    

    五、async await的作用及应用场景

    async函数返回一个Promise对象,可以通过await关键字来暂停函数的执行,等待Promise对象的状态变为resolved后继续执行,可以更简洁地处理异步操作。

    async await这两个命令是成对出现的,如果使用await没有在函数中使用async命令,那就会报错,如果直接使用async没有使用await不会报错,只是返回的函数是个promise,可以,但是没有意义,所以这两个一起使用才会发挥出它们本身重要的作用。

    function getData() {
      //异步
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("结果");
        });
      });
    }
    async function test() {
      const resultData = await getData();
      console.log(resultData);
    }
    test();
    

    相较于Promise,async await有何优势?

  • 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
  • 和同步代码更一致的错误处理方式( async await 可以⽤成熟的 try catch 做处理,比 Promise 的错误捕获更简洁直观)
  • 调试时的阅读性, 也相对更友好
  • 赞(0) 打赏

    评论 抢沙发

    觉得文章有用就打赏一下文章作者

    非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

    支付宝扫一扫

    微信扫一扫