重学JS | 异步编程 Promise

325 阅读3分钟

这是我参与更文挑战的第9天,活动详情查看:更文挑战

[重学JavaScript系列文章连载中...]

我们都知道JavaScript是属于“单线程”语言,一次只能完成一件任务,不能同时执行多个js代码任务。倘若存在耗时的任务,后续任务只能排队等待,这将拖延整个程序的执行,导致页面卡顿,甚至造成页面假死。为了解决这个问题,JavaScript语言引入了同步任务和异步任务,本文讲讲异步编程。

callback()回调函数

首先看看采用callback()回调函数处理异步。Ajax异步请求可能是最常见的异步编程了。看下例子:

$.ajax({
	url:'/testurl',
  success:function(){ 
  	// 回调处理请求成功
  }
})

倘若存在多个嵌套请求,代码将陷入“回调地域”。导致的问题有:

  1. 代码臃肿,可读性差
  2. 代码耦合度高、可维护性差、难以复用
  3. 回调函数都是匿名函数,不方便复用

下面看看ES6的Promise,为异步编程提供了更合理的解决方案。

Promise生命周期

Promise对象存在三种状态,进行中(pending),已成功(fulfilled)和已失败(rejected)。Promise创建时处于pending状态,状态的改变只能两种可能。一种是成功的时候,将pending状态改变为fulfilled;另一种是失败时将状态改变为rejected。

Promise执行顺序

const promise = new Promise((resolve,reject)=>{
   console.log(1)
   resolve()
   console.log(2)
})
promise.then(res=>{
	console.log(3)
})
console.log(4)
promise.then(res=>{
	console.log(5)
})
// 执行输出顺序为:1,2,4,3,5
  1. Promise创建后会立即执行,输出1。
  2. 执行resolve()函数,触发then()函数指定的回调函数,但是它要等当前线程中的同步代码执行完毕,因此依次输出2,再输出4
  3. 当所有同步代码执行完毕,执行then()函数,此时存在两个回调,依次执行输出3、5。

Promise处理异常

我们通常建议使用catch()去捕获Promise异常,这就使catch()函数和then()函数成对出现,catch()接收的就是执行rejected()函数传递的参数。

为啥建议采用catch()函数呢?看个例子

Promise.resolve()
  .then(res=>{
		throw new Error('error')
	},err=>{
  	console.log('err',err)
}).catch(err=>{
	 console.log('err',err)
})

这个例子里then()函数的第二个函数不能捕获第一个函数抛出的异常,而catch()函数能捕获到第一个函数的异常,这就是建议采用catch()处理rejected状态的原因。

Promise.all()

const p = Promise.all([p1,p2,p3,p4])

p的状态由all()函数接收的所有Promise状态决定,只有所有接收的Promise实例状态都变为fulfilled,则p的状态才会变成fulfilled。其中存在一个rejected状态,则p状态为rejected。需要注意的是当接收的Promise实例已经定义了catch()函数,则其rejected状态会自行捕获消化,并不会触发Promise.all()函数的catch()函数。

const p1 = new Promise((resolve,reject)=>{
	resolve(1)
})
const p2 = new Promise((resolve,reject)=>{
	throw new Error('error')
}).catch(err=>err)

Promise.all([p1,p2]).then(res=>{
	console.log(res)  // [1,'error']
}).catch(err=>{   
  // 此处p2的异常并未被Promise.all捕获,而是自行消化。
  // p2执行完catch()函数后,p2的状态实际为fulfilled。
	console.log('异常',err)
})

Promise.race()

const p = Promise.race([p1,p2,p3])

如果接收的多个Promise实例中任何一个实例变化,那么p实例的状态就随之改变,最先改变的那个Promise实例返回值将作为实例p的回调函数入参。

可以实现这么个场景,Ajax请求3s未收到请求成功响应,则自动处理成请求失败。

const p1 = ajaxGet('testUrl')
const p2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
  	reject(new Error('request timeout 3s'))
  },3000)
})
const p = Promise.race([p1,p2])

Promise.resolve()

将传入的变量转换为Promise对象,等价于在Promise函数内调用resolve()函数。 Promise.resolve()函数执行后,Promise的状态会立即变为fulfilled,然后进入then()函数中处理。

Promise.resolve()
// 等价于
new Promise(resolve=>resolve())

Promise.reject()函数一样,只不过进入的是catch()函数。这里不再叙述。

至此,我们大体学习了异步编程之一的Promise。