Promise异步编程

81 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、回调函数

回调函数本身是同步代码 通常在编写JavaScript代码时,使用的回调嵌套的形式大多是异步函数,所以会导致部分开发者认为凡是回调形式的函数都是异步流程。其实并不是这样的,真正的解释是:JavaScript中的回调函数结构,默认是同步的结构,由于JavaScript单线程异步模型的规则,如果想要编写异步的代码,必须使用回调嵌套的形式才能实现,所以回调函数结构不一定是异步代码,但是异步代码一定是回调函数结构。 采用默认的上下结构永远拿不到异步回调中的结果,这也是为什么异步流程都是回调函数的原因 。

二、Promise介绍

注意:Promise是同步方法,promise.then是异步方法

 console.log('--开始----');
      const p = new Promise(function(resolve, reject) {
        console.log('调用Promise');
        // resolve可以触发then中的回调执行
        resolve('执行了resolve');
        // reject('异常了');
      });
      p.then(res => {
        console.log('then 执行', res);
      })
        .catch(err => {
          console.log('catch 执行', err);
        })
        .finally(() => {
          console.log('finally 执行');
        });
      console.log('--结束----');
	  
	  执行结果:
      --开始----
      调用Promise
      --结束----
      then 执行 执行了resolve
      finally 执行

通过上例,我们可以知道new Promise中的回调函数确实是在同步任务中执行的,其次是如果这个回调函数内部没有执行resolve或者reject那么p对象后面的回调函数内部都不会有输出,而运行resolve函数之后.then和.finally就会执行,运行了reject之后,catch和finally就会执行。

Promise的组成

每一个Promise对象都包含三部分

  1. [[Prototype]]: 代表Promise的原型对象
  2. [[PromiseState]]: 代表Promise对象当前状态
  3. [[PromiseResult]]: 代表Promise对象的值,分别对应resolve或reject传入的结果

Promise的三种状态:

  • pending:初始状态,也叫就绪状态,这是在Promise对象定义初期的状态,这时Promise仅仅做了初始化并注册了他对象上所有的任务。
  • fulfilled:已完成,通常代表成功执行了某一任务,当初始化中的resolve执行时,Promise的状态就变更为fulfilled,并且then函数注册的回调函数会开始执行,resolve中传递的参数会进入回调函数作为形参。
  • rejected:已拒绝,通常代表执行了一次失败任务,或者流程中断,当调用reject函数时,catch注册的回调函数就会触发,并且reject中传递的内容会变成回调函数的形参。

Promise的链式调用

链式调用的本质就是在调用这些支持链式调用的函数的结尾时,又返回了一个包含他自己的对象或一个新的自己,这些方式都可以实现链式调用。

// 链式调用
function MyPromise() {
return this;
}
MyPromise.prototype.then = function() {
console.log('触发了then');
return this;
};
new MyPromise().then().then();

三、Promise的注意事项

链式调用

// 链式调用的注意事项
let pr = new Promise((resolve, reject) => {
	resolve('promise实例');
});
pr.then(res => {
	console.log('res'); // undefined
	return '123';
})
	.then(res => {
	console.log('res'); // 123
	return new Promise(resolve => {
		resolve(456);
	});
})
	.then(res => {
	console.log('res'); // 456
	return '直接返回的结果';
})
	.then()
	.then('字符串')
	.then(res => {
	console.log('res'); // 直接返回的结果
});

1、只要有then()并且触发了resolve,整个链条就会执行到结尾,这个过程中的第一个回调函数的参数是resolve传入的值 2、后续每个函数都可以使用retrun返回一个结果,如果没有返回结果的话,下一个then中回调函数的参数就是undefined 3、返回结果如果是普通变量,那么这个值就是一个then中回调函数的参数 4、如果返回的是一个Promise对象,那么这个Promise对象resolve的结果会变成下一次then中回调的函数的参数【直接理解为,返回Promise对象时,下一个then就是该对象的then】 5、如果then中传入的不是函数或者未传值 ,Promise链条并不会中断then的链式调用,并且在这之前最后一次的返回结果,会直接进入离它最近的正确的then中的回调函数作为参数

Promise中断

有两种形式可以让then的链条中断,如果中断还会触发一次catch的执行

// 链式调用的中断
      let pe = new Promise((resolve, reject) => {
        resolve('Promise的值');
      });
      pe.then(res => {
        console.log(res);
      })
        .then(res => {
          // 两种方式中断Promise
          // throw '通过throw中断';
          return Promise.reject('reject中断');
        })
        .then(res => {
          console.log(res);
        }).catch(res => {
          console.log('catch执行', res);
        });;

四、Promise常见的API

1. Promise.all()

当我们在代码中需要使用异步流程控制时,可以通过Promise.then来实现让异步流程一个接一个的执行,假设有三个接口,并保证三个接口的数据全部返回后,才能渲染页面。如果a耗时1s、b耗时0.8s、c接口耗时1.4s,如果用Promise.then来执行流程控制,如果通过then函数的异步控制,必须等待每个接口调用完毕才能调用下一个,这样总耗时就是 1+0.8+1.4=3.2s。这种累加显然增加了接口调用的时间消耗,所以Promise提供了一个all方法来解决这个问题: Promise.all([promise对象,promise对象,...]).then(回调函数) 回调函数的参数是一个数组,按照第一个参数的promise对象的顺序展示每个promise的返回结果。 借助Promise.all来实现,等最慢的接口返回数据后,一起得到所有接口的数据,那么这个耗时将会只按照最慢接口的消耗时间1.4s执行,总共节省了1.8s. Promilse.all相当于统一处理了多个promise任务,保证处理的这些所有promise对象的状态全部变成为fulfilled之后才会触发all的.then函数来保证将放置在all中的所有任务的结果返回

2.Promise.race()

Promise.race([promise对象,promise对象,...]).then(回调函数) 回调函数的参数是前面数组中最快一个执行完毕的promise的返回值。 Promilse.race()相当于将传入的所有任务进行了一个竞争,他们之间最先将状态变成fulfilled的那一个任务就会直接的触发race的.then函数并且将他的值返回,主要手于多个任务之间竞争时使用

五、Async和Await

async和await相当于使用了自带执行函数的Generator函数,所以async和await逐渐成为主流异步流程控制的终极解决方案。

    async handleTest() {
      console.log('1');
      // this.test();// await方法
      this.promisTest(); // 等价promise方法
      console.log('2');
      // 输出顺序:1,3,2,4
    },
    // await方法
    async test() {
      console.log(3);
      var a = await 4;
      console.log(a);
    },
    // 此方法是test方法的promsise写法
    promisTest() {
      new Promise(resolve => {
        console.log(3);
        resolve(4);
      }).then(res => {
        console.log(res);
      });
    }

async函数中有一个最大的特点,就是第一个await会作为分水岭一般的存在,在第一个await的右侧和上面的代码,全部是同步代码区域相当于new Promise的回调,第一个await的左侧和下面的代码,就变成了异步代码区域相当于then的回调。

总结

从回调地狱到Promise的链式调用到Generator函数的分步执行,再到async和await的自动异步代码同不化机制,经历了很长时间。Promise和事件循环系统并不是JavaScript中的高级知识,而是真正的基础知识。