Promise


Promise

Promise 对象用于表示一个异步操作的最终完成或失败及其结果值,是一种比传统的回调函数和事件调用更加合理的异步结果处理方案。

Promise 并不能创建异步任务,它只是对传统回调函数的改进和封装,常用于封装异步请求,以解决回调地狱等问题;Promise利用微任务使被封装的回调可被加入到当前事件循环中执行。

Promise 可解决的问题:

  1. 解决回调地狱的问题
  2. 支持多个请求并发

Promise 的缺点:

  1. 无法取消 Promise,一旦新建就会立即执行,无法中途取消。
  2. Promise 内部抛出的错误,不会反应到外部。
  3. 当处于 pending状态时,无法得知目前进展到哪一个阶段


回调地狱

异步操作时,在请求成功的回调函数里继续写函数,或继续进行异步操作,层层嵌套,就会形成回调地狱。


Promise 的特点

Promise 有三种状态:pending(未完成)、fulfilled(已成功)、rejected(已失败)

Promise 对象的状态改变,只有两种可能:

  1. 从 pending(未完成)变为 fulfilled(已成功)
  2. 从 pending(未完成)变为 rejected(已失败)

一旦状态改变,就不会再变,任何时候都可以得到这个结果


基本使用

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject

Promise 对象可以用 then 方法分别指定 resolved 和 rejected 状态的回调函数。

let promise = new Promise(function (resolve, reject) {
  if (/* 异步操作成功 */) {
    // resolve函数将Promise的状态从“未完成”变为“成功”
    resolve(value);
  } else {
    // reject函数将Promise状态从“未完成”变为“失败”
    reject(error);
  }
});


// then 方法有两个参数,第一个是成功 resolve 的回调,第二个是失败 reject 的回调
promise.then(
  // Promise对象状态为“成功”时执行
  function (value) {
    
  },
  // Promise对象状态为“失败”时执行
  function (error) {
 
  }
)

也可以使用如下写法:catch 方法相当于 then 方法的第二个参数,即失败 reject 的回调。

promise.then( // Promise对象状态为“成功”时执行
  function (value) {    }
).catch(  // Promise对象状态为“失败”时执行
  function (error) {   }
)


执行过程:

  1. Promise 新建后会立即同步执行其中的代码
  2. 调用 resolve 和 reject 并不会像return一样终结 Promise 参数函数的执行,其后的代码还是会被执行
  3. 当前脚本所有同步任务执行完后才会执行 then 回调;即,不会在本轮事件循环中执行then回调
let promise = new Promise(function (resolve, reject) {
  console.log(2)
  resolve(3)
  console.log(4)
})

// then的第二个函数是可选的,非必须。
promise.then(function (value) {
  console.log(value)
})

console.log(5)

// 2
// 4
// 5
// 3


then() 的链式调用

then 中通过 return 返回普通数据时,返回的数据会成为下一个 then 的实参;如果通过 return 返回 Promise 对象时,下一个 then 就会是该对象的 then , 并在该对象执行完成后调用;

例1:then 方法定义在原型对象 Promise.prototype 上,then返回的是一个新的 Promise 实例,因此可以继续调用 then 方法,实现链式调用。多个then链式调用时,前一个then返回结果会成为下一个then的实参;

new Promise((resolve, reject)=> {
  resolve(1)
})
.then(val => { // 状态为成功时执行
   console.log(val)
   return 2
})
.then(value => { // 状态为成功时执行
   console.log(value)
})
  
// 1
// 2


例2:当前then 返回 Promise 对象时,后面的 then 会等待这个 Promise 对象的状态发生变化,才会被调用

function getNum (num) {
  let promise = new Promise((resolve, reject)=> {
    setTimeout(() => {
      resolve(num)
    }, 1000)
  })
  return promise
}

getNum(1)
.then(val => {
   console.log(val)
   return getNum(2) // 返回Promise对象
})
.then(val => {
   console.log(val)
})
  
// 1
// 2

第一个 then 执行完后返回了一个 Promise 对象,一秒后,返回的 Promise 对象状态从 “未完成” 变为 “成功” ,此时开始执行第二个 then 方法里的回调函数。并且第二个then参数是这个新  Promise 对象中 resolve(num) 中的实参


catch() 方法

跟 then 一样,catch 也定义在原型对象 Promise.prototype 上

let promise = new Promise((resolve, reject)=> {
  reject('出错啦')
})

promise.then(val => console.log(val))
       .catch(err => console.log(err))

// 等同于

promise.then(val => console.log(val))
       .then(null, err => console.log(err))

// 出错啦
// 出错啦


特性说明演示:

let promise = new Promise((resolve, reject)=> { // 解析一
  // 这里的代码是在主队列中同步执行,所谓的异步是指Promise的原型方法then、catch、finally,它们会被放在任务队列中等待下一次事件循环时执行
  resolve('Promise实例正确')          
  throw new Error('Promise实例出错'); // 此处抛出异常无效,因为Promise状态已变成resolved, Promise状态不会二次修改,所以抛出的错误不会被捕捉
})

promise.then(val => { // 解析二
    console.log(val)
    throw new Error('then方法内出错')  // 此处抛出异常不受Promise状态影响,会被下一个catch捕获
    return '链式调用then'
  })
  .then(val => { // 解析三
    console.log(val)
  })
  .catch(err => { // 解析四
    console.log(err)
    throw new Error('catch方法内出错') // 此处抛出异常不受Promise状态影响,会被下一个catch捕获
  })
  .catch(err => { // 解析五
    console.log(err)
    // 因为之后没有catch了,所以此处的异常会被浏览器打印至控制台上;但并不影响程序正常执行,因为Promise对象内抛出的错误不会抛到外部,所以不会导致进程退出或脚本终止执行
    throw new Error('最后的catch方法出错') 
  })

setTimeout(() => { // 解析六
  console.log('promise后调用') // 因为此处的异步程序是最后一个被放置到任务队列中,因此最后执行
}, 0)

// Promise实例正确
// Error: then方法内出错
// Error: catch方法内出错
// Uncaught (in promise) Error: 最后的catch方法出错
// promise后调用


finally() 方法

finally 方法不管 Promise 对象状态是什么,都会执行。

finally 不接受任何参数,且总是返回原来的值。

new Promise((resolve, reject) => {
  resolve(1)
})
.finally(() => {
  console.log(2)
})
.then(val => console.log(val))

// 2
// 1


async/await

async/await是用于把复杂难懂的异步代码变成类似同步代码的语法糖;通常情况下,其作用就是用于简化Promise的使用:

let proObj = ()=>{
   new Promise(function(resolve, reject) {
     if (/* 异步操作成功 */) {
        resolve(value); //将Promise的状态由padding改为fulfilled
     } else {
        reject(error); //将Promise的状态由padding改为rejected
     }
   })

}

const handleGuess = async () => {
  try {
    const result = await proObj(); // 代替then方法,await会使用promise直接获得then结果
  } catch (error) { 
    alert(error);  // catch方法可以由try、catch来替代
  }
};

handleGuess()

通过在函数前使用async关键字,可以创建了一个异步函数,在async函数内的使用方法较之前有如下不同:

  • 将await关键字放在Promise前,就可以直接获得then结果。
  • 使用try, catch语法来代替promise中的catch方法


async/await在fetch请求中应用:

const fetchCountry = async (alpha3Code) => {
   try {
      const res = await fetch('https://restcountries.eu/rest/v2/alpha/' + alpha3Code);
      const data = await res.json();
      return data.borders;
   } catch (error) {
      console.log(error);
   }
};

fetchCountry('cn')


all() 方法

Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 all 方法并行执行所有异步操作,并在所有异步操作执行完成后才执行自己的回调。

例1:基本使用

如下:all 方法接受一个 Promise 对象数组,数组中所有Promise对象的结果均为 fulfilled fruits 的状态才会变成 fulfilled ,数组中任何一个Promise对象被rejectedfruits的状态就变成 rejected,同时第一个被 reject 的实例的返回值,会返回给 fruits 的回调catch。如果数组中某个Promise对象定义了自己的catch回调,则一旦被rejected,并不会触发 Promise.all() 的 catch 方法,而是会触发它自己的catch,它自己的catch方法返回的是一个新的 Promise 实例,所以实际上这个Promise对象指向的是新的Promise实例,该实例执行完 catch 方法后,也会变成 resolved,最终导致数组中这个Promise对象的状态变成 resolved

let promise1 = new Promise((resolve, reject) => {
  resolve(1)
})

let promise2 = new Promise((resolve, reject) => {
  throw new Error('出错了')
}).catch(err => err)

let promise3 = new Promise((resolve, reject) => {
  reject('又出错了')
})

let fruits = Promise.all([promise1, promise2, promise3])

fruits.then(val => {
  console.log(val)
}).catch(err => {
  console.log('捕捉到某个Promise对象出错:”' + err + '”')
})
  
// 捕捉到的出错:”Error: 出错了”


例2:使用async/wait

Array的 map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]


当处理一个数组Promise时,需要使用Promise.all:

const fetchCountryAndNeigbors = async () => {
   
   // a. 获取一个then结果
   const then = await fetchCountry("cn");
   
   // b. 同时获取多个Promise的then结果
   // Promise.all同时接收并返回Promise数组所有Promise的then结果
   const thenResultArray = await Promise.all(
      ['zh-cn','zh-hk'].map((ele) => fetchCountry(ele)); // 此行返回Promise数组,作为Promise.all的参数
   );

   console.log(neighbors);
};

fetchCountryAndNeigbors();


race() 方法

与 all() 相同,race() 也是用于并发处理多个异步任务,区别在于 all () 是在所有任务都完成后才能得到结果。而 race() 是只要有一个任务执行完成,就能得到结果。


Promise回调特性:

  • 在本轮 事件循环 运行完成之前,回调函数是不会被调用的。
  • 通过多次调用 then() 可以添加多个回调函数,它们会按照插入顺序进行执行。


注意事项

1.0> 如下,调用链中如果没有定义catch,但却执行了reject,则会报错 Uncaught(in promise),即在Promise中未捕获异常;

但没有定义 then 的情况下,执行 resolve 却不会报出异常;

new Promise(function(resolve, reject) {
   if (/* 异步操作成功 */) {
      resolve(value); //将Promise的状态由padding改为fulfilled
   } else {
      reject(error); //将Promise的状态由padding改为rejected
   }
})


2.0> 创建Promise对象快捷方式

创建Promise对象,并立即执行resolve

Promise.resolve(value)
等同于
new Promise(function(resolve){
    resolve(value);
});

Promise.reject也是同理;


3.0> 除了执行 reject 会执行 catch 外,其它任何异常的抛出都会导致 catch 被执行;


4.0> Promise内部用try catch 来捕获的异常,而try catch不能捕获异步异常;

如下,发生异步异常时只会在控制台报错,但不会执行rejectcatch函数;

new Promise((resolve, reject) => {
    setTimeout(() => {
        throw new Error('nono') // 主动抛出异步错误
    }, 500);
}).then(()=>{},(err)=>{
    console.log(1) // 这里是reject时应该调用的函数,但是这里并没有执行,只会在控制台报错
}).catch((err)=>{
    console.log(2) // 这里catch都不执行
    console.log(err)
})

异步异常只能通过如下方式捕获

setTimeout(() => {
    try {
        throw new Error('出错了')
    } catch (e) {
        console.log(e)//代码执行的时候,这里就能捕获到上面抛出的异常错误了
    }
}, 400)


5.0> axios完全支持Promise,其拦截器、then、catch与Promise调用完全相同;即,拦截器中的return,会直接触发后续的then;


详见:

https://chenminzhe.com/2020/04/05/ES6%E4%B8%ADPromise%E8%AF%A6%E8%A7%A3/

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises



举报

© 著作权归作者所有


1