Promise概念总结篇

142 阅读8分钟

前言

关于Promise的知识点,一定要认真多读几遍阮一峰老师的ES6电子书,照着案例demo自己手写几遍,熟悉每个知识点已经promise的概念,特性和原理。本文是阅读了阮一峰老师的文档然后自己总结下自己的理解。基本是搞懂了promise的概念,后面计划再写一篇手写promise源码和结合实际需求的应用场景再总结一篇业务中使用promise对象的使用方法。

文中有错误的地方,欢迎大佬指正。

概念

Promise 是异步编程的一种解决方案,可以将异步任务按同步方式处理。从理解上说Promise是一个容器,里面存放异步任务。在语法上 Promise是一个对象,有统一的API处理各种异步操作。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

基本语法

Promise对象是一个构造函数,可以生成实例

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 构造函数接受一个函数作为参数,该函数内还有两个参数,resolve和reject也是两个函数。

resolve函数的作用是将 状态从 pending 变为 fulfilled,即进行中到已成功,一般在异步任务执行成功后调用,并且会将执行的结果以参数形式传递出去 resolve(data)。

reject函数的作用是将状态从 pending 变为 rejected,即进行中到已失败,一般在异步任务执行失败后调用,也会将失败信息传递出去 reject(error)

Promise构造函数生成实例后,可以使用then()方法 来指定对应 resolve 和reject 的回调函数

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then((res)=>{
	//succeess 对应resolve函数的回调
  //res的值是从resolve函数的参数传递出来的
  console.log(res)
}, (err)=>{
		//failure 对应reject函数的回调
   //err 的值是从reject函数的参数传递出来的
});

then() 方法 里面有两个回调函数作为参数,第一个回调函数就是 状态变为 resolved 时调用,第二个回调函数是状态变为 rejected是调用。

两个回调函数是可选的,同时两个回调函数的参数也都是接收 resolve 和reject 传递出来的参数。

const timeout = (ms) => {
  return new Promise(function (resolve, reject) {
    setTimeout(()=>{resolve('resolve')}, ms)
  })
}

timeout(1000).then((res) => {
  console.log(res)
})

//1秒后 resolve

特点

1)Promise对象状态不受外界影响

Promise 有三种状态 进行中(pending)已成功(fulfilled)已失败(rejected)

状态依据异步操作的结果来改变,其他操作无法改变状态。

//Promise的状态只受执行了 resolve或者reject 回调函数来改变状态,Promise内部其他的代码不会改变状态

new Promise(()=>{})

//Promise {<pending>}
//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined
         
new Promise((resolve)=>{
    resolve()
})
//Promise {<fulfilled>: undefined}
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: undefined
         
new Promise((resolve,reject)=>{
    reject()
})
//Promise {<rejected>: undefined}
//[[Prototype]]: Promise
//[[PromiseState]]: "rejected"
//[[PromiseResult]]: undefined

//没有执行 resolve 或者 reject 回调函数 其他的同步,异步任务执行不会改变
new Promise(function (resolve, reject) {
   console.log(234)
})
//234
//Promise {<pending>}
//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined

 new Promise(function (resolve, reject) {
  setTimeout(() => console.log(234), 3000) 
})

//Promise {<pending>}
 //[[Prototype]]: Promise
 //[[PromiseState]]: "pending"
 //[[PromiseResult]]: undefined

2)Promise状态一旦执行不会再改变

promise执行了回调函数以后 状态会进行以下改变

pending --> fulfilled 进行中变为成功

pendign -- > rejectd 进行中变为失败

状态一旦改变就稳定会一直保持这个结果不会再变,再添加回调函数也还是这个状态

//执行了resolve 回调函数 状态变化,后续再执行reject回调 也不会再改变状态
new Promise((resolve, reject) => {
  resolve()
  reject()
}).then((res) => {
  console.log(res)
}).catch((err) => {
  console.log(err)
})

//resolve
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: undefined

3)比较注意的点

1.新建 Promise 实例会立即执行,无法中途取消

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

新建Promise实例 会立即执行,首先执行 内部的同步任务打印出 'Promise',后面then()方法是异步任务暂时挂起,接着执行后面的同步任务 打印出 'Hi' ,然后再执行异步任务 打印出 ' resolved'

可以使用函数封装后,调用函数再执行Promise操作

const pFun = ()=>{
  return new Promise((resolve)=>{
    console.log(123)
  })
}
pFun()
// 123

2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部

//如果不执行回调函数,内部抛出的错误不会反应到外部
new Promise(function (resolve, reject) {
   new Error('123')
})

//Promise {<pending>}
//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined

3.同样不执行回调函数,Promise内部异步任务执行完的结果不会传递到外面

//Promise内没有执行 resolve回调函数 ,所有then函数没有执行,结果没有传递出来
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// Promise
// Hi!


//执行resolve回调函数
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
//Promise
// Hi!
//resolved.

4)回调嵌套

如果p2 的resolve函数的参数是另一个p1,一个异步操作的结果返回了另一个异步操作。这时候p2的状态是由p1决定的,下面代码 p1始终是pending,p2虽然执行了resolve,但接收参数是p1,所以p2的状态也是pending而不是 resolved

const p1 = new Promise(()=>{})

const p2 = new Promise((resolve, reject)=>{
  resolve(p1)
})

p2.then((res)=>{
  console.log(res)
})

//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined

下面代码p1是3秒后reject ,p2是一秒后执行resolve,参数是p1 ,此时p1是等待状态,所以p2也是 pending,3秒后 p1 reject Error ,p2的 catch 会捕获异常错误。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('fail'))
  }, 3000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(p1), 1000)
})

p2.then(res => console.log(res)).catch(err => console.log(err))

//Promise {<pending>}
//[[Prototype]]: Promise
//[[PromiseState]]: "pending"
//[[PromiseResult]]: undefined
//Error: fail

5)调用resolve 或者reject 不会中断后面的任务执行,并且如果是同步任务还会先执行,then方法属于异步任务

new Promise((resolve, reject)=>{
  resolve(123)
  console.log(345)
}).then(res=>{console.log(res)})
//345
// 123


new Promise((resolve, reject) => {
  reject(new Error('fail'))
  console.log('error')
}).catch(err => console.log(err))

//error
// Error: fail

Promise.prototype 原型的方法

1)Promise.prototype.then()

Promise实例拥有then方法,then是定义 在Promise的原型对象上的,作用是给Promise实例添加 resolved和rejected 状态改变的回调函数,then方法有两个参数,都是回调函数。第一个是resolved的回调函数,第二个是rejected的回调函数。then()方法返回的是新的Promise实例,不是原来的Promise实例。因此then方法可以采用链式调用,then()调用后面再接另一个then(),可以处理异步操作嵌套。

const getList = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({infoId: '1234'})
    }, 1000)
  })
}

const getInfo = ({infoId}) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({infoId,datas: '...infoDatas'})
    }, 1000)
  })
}

// 因为getList和 getInfo里 是resolve,没有判断执行reject函数,使用try catch捕获外部异常。
try {
  getList().then((params) => {
    return getInfo(params)
  }).then((result) => {
    console.log('result:', result)
  }).catch(err => console.log(err))
} catch (e) {
  console.log(e)
}

//result: {infoId: '1234', datas: '...infoDatas'}

2)Promise.prototype.catch()

捕获 Promise 实例的 rejected状态的异常,也是 Promise构造函数 原型上的方法

catch方法 参数是一个回调函数 和then方法的第二个回调函数一样 ,回调函数的参数接收 rejected状态 传递出来的错误信息。

const p3 = new Promise((resolve, reject)=>{
  reject(new Error('error'))
})

p3.catch(err=>console.log(err))
//Error: error

//catch 和then 的第二个回调函数一样 处理抛出来的错误异步操作, 如果都加上 执行了then方法就不再执行 catch 方法
const p3 = new Promise((resolve, reject) => {
  reject(new Error('error'))
})

p3.then(null, (err) => console.log('then:', err))
  .catch(err => console.log('catch:', err))

//then: Error: error

注意点: then() 方法指定的回调函数,如果运行中抛出错误,也会被 catch() 方法捕获。

const p = new Promise((resolve, reject)=>{
    throw new Error('error info')
})
p.catch(err => console.log(err))

// Error: error info

如果执行了resolve 状态已经改变为fulfilled,后面再抛出异常catch也不会捕获到了。

const p = new Promise((resolve, reject) => {
  resolve('is ok')
  throw new Error('error info')
})

p.then(res => console.log(res))
  .catch(err => console.log(err))
// is ok

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getList('/a.json').then(res => {
  return getInfo(res.url)
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

总共三个promise对象,其中一个promise实例,两个then()方法,任何一个抛出错误,都会被最后一个catch()方法捕获到,而then()的第二参数只能捕获当前promise对象的异常错误, 所以尽量使用catch()方法来处理异常错误信息。

如果没有写catch(),promise内部会吃掉错误

跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

const asyncThing = ()=>{
  return new Promise((resolve, reject)=>{
    resolve(x+1)
  })
}
asyncThing().then(()=>{
  console.log('is ok')
})
setTimeout(()=>{
  console.log(123)
},1000)

// Uncaught (in promise) ReferenceError: x is not defined
//123

x没有声明,promise内部报错会打印出错误信息,但是后面的任务还是继续执行了,promise内部错误没有影响到外部的代码执行

3)Promise.prototype.finally()

不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。finally 回调函数不接收任何参数,也不依赖状态是否 fulfilled 或者 rejected

const getDatas = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('results')
    }, 1000)
  })
}

const queryDatasHandle = () => {
  setLoading(true)
  getDatas().then((res) => {
    console.log(res)
  }).catch(err => console.log(err))
    .finally(() => {
      setLoading(false)
    })
}

queryDatasHandle()