Promise,从陌生到熟悉

98 阅读8分钟

Promise

Promise 是什么?

Promise 简单来说,是一种异步编程解决方案,主要用来解决大资源导致页面阻塞问题,还可以来解决地狱回调,让代码更加优雅。

概况

Promise 对象有个特性,一旦创建就会立即执行,即使它本身是个异步函数。

const prom = new Promise((resolve)=>{
    console.log('创建了一个Promise');
    resolve('Promise 回调')
})

prom.then(res=>{
    console.log(res);
})

console.log("某个同步任务");

// 输出结果

// 创建了一个Promise
// 某个同步任务
// Promise 回调

我们可以看到第一个打印的是 创建了一个Promise,所以要注意 Promise 一旦创建就会立即执行的特性。

内置状态

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

一个promise执行结束,要么是 已兑现 ,要么是 已拒绝

image.png

解决的痛点

解决回调地狱!!!!

有这么个情况,某个接口需要依赖另外一个接口的值,那么以前的写法是:

请求1 (function (请求结果1) {
  请求2 (function (请求结果2) {
      请求3 (function (请求结果3) {
      })
  })
})

有了Promise,可以这么写:

const p = new Promise((resolve)=>{
    setTimeout(function() {
        resolve("接口请求成功-1")
    }, 10);
})

p.then(res=>{
    console.log("接口1的值", res)
    return new Promise((resolve)=>{
        setTimeout(function() {
            resolve("接口请求成功-2")
        }, 10);
    })
}).then(res=>{
     console.log("接口2的值", res)
     console.log("执行接口3")
})

使用

Promise是一个全局的内置对象,通过 new Promise 可以创建一个 Promise 对象。 接收一个函数,回调了resolve(成功回调),reject(失败) 两个函数。

new Promise((resolve, reject)=>{
   console.log('创建一个Promise');
});

// Promise 创建就会立即执行
// 创建一个Promise

then、catch以及finally

const promise = new Promise((resolve, reject)=>{
    resolve("成功...")
});
const promise2 = new Promise((resolve, reject)=>{
    reject("失败...")
});

promise.then(res=>{
    console.log(res) // 成功...
}).finally(()=>{
    console.log("成功了执行")
})

promise2.catch(res=>{
    console.log(res) // 失败...
}).finally(()=>{
    console.log("失败了也执行")
})

  • resolve 通过 .then 捕获 resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

  • reject 通过 .catch 捕获 reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

  • finally 不管调用成功与否,都一定会执行,适合处理一些固定执行的逻辑

链式调用

Promise 中 return 的数据,会被自动包成 Promise 对象,可以通过 .then 或 .catch 二次捕获。

  • 直接 return 会自动 包装 成一个 promise 实例,状态是 fulfilled,并且可以通过 .then捕获
  • return 一个 reject,可以在 catch 中 被捕获.
  • return 一个 resolve,可以在 then 中捕获

var flag = true
var p = new Promise((resolve, reject) => {
    console.log("创建一个 promise");
    setTimeout(()=> {
        resolve(flag)
    }, 100)
})

p.then(res => {
    console.log('then 1')
    if(res) {
        console.log('reject from then 1')
        return Promise.reject(res)
    }   
    return res
}).then(res => {
    console.log('then 2')
}).catch(err => {
    console.log('catch 1')
    return err
}).catch(err => {
    console.log('catch 2')
}).then(res => {
    console.log('then 3')
    return Promise.reject(res) 
}).catch(err=>{
    console.log('catch 4')
    return Promise.resolve(err) 
}).then(res => {
    console.log('then 5:', res)
}).then((res)=>{
    console.log('即使不return也会调用 then')
})

返回结果如下

创建一个 promise
then 1
reject from then 1
catch 1
then 3
catch 4
then 5true
即使不return也会调用 then

async/await

其实是 Promise 的一种语法糖。像结合了 Generators 和 Promise
async/await 就是为了解决Promise冗余的 .then,让代码更加简洁,语义化。

await

  1. await的意思就是等待。它后面可以跟一个表达式
  • 如果是值(如字符串、数字、普通对象等等)的话,返回值就是本身的值。
  • 是一个promise对象。await会等待这个promise的状态由pending转为fulfilled或者rejected。在此期间它会阻塞,延迟执行await语句后面的语句。
    如果promise对象的结果是resolve,它会将resolve的值,作为await表达式的运算结果。
  1. 使用await 必须结合 async使用,需要包裹在 async 函数中

写个例子:

// 模拟请求后端接口
function asyncFn () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (true) {
        console.log('resolve console')
        resolve('resolve return')
      } else {
        reject('reject return')
      }
    }, 2000)
  })
}

// promise
asyncFn().then((res) => {
  console.log("promise等待", res)
}, (err) => {
  console.log(err)
})

// await,需要结合 async
async function awaitFn(){
   try {
      var res = await asyncFn()
      console.log("await等待-promise resolve的值:", res)
      var num = await 5;
      console.log("await等待-普通对象:", num);
      // ...甚至更多接口
   } catch(err) {
      console.log(err)
   }
}
awaitFn();

输出结果如下:

resolve console
promise等待 resolve return

resolve console
await等待-promise resolve的值: resolve return
await等待-普通对象:5

async

await 函数会强行阻塞代码运行,使用 async 声明,会将函数包了一层 promise,这样内部的异步操作其实就是由pending转为resolve或者reject的过程。这样函数本身就能够随意调用,函数内部的await也不会再影响到函数外部的代码执行。 简单来说就是, async把函数转为promise对象,可以解决 await强制阻塞的缺点

async function asyncFn () {
  return 'async'
}
console.log(asyncFn())


console.log(asyncFn().then((res)=>{
    console.log("值",res)
}))

会发现打印的是一个promise对象。而且是Promise.resolve对象。resolve的值就是asyncFn的函数返回值async

特点总结

  1. await 会阻塞代码
  2. 使用 async包裹的函数,会变成一个 promise 对象,接收的是 resolve 的值
  3. await 必须配合 async 使用

常用方法

resolve

返回一个以给定值解析后的 Promise 对象。如果这个值是一个 promise,那么将返回这个 promise;如果这个值是 thenable(即带有 then 方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。
一般用来处理接口调用成功

// 返回一个普通数组
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});


// 返回一个 promise 对象
var original = Promise.resolve(33);
var cast = Promise.resolve(original);
cast.then(function(value) {
  console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));

/*
*  打印顺序如下,这里有一个同步异步先后执行的区别
*  original === cast ? true
*  value: 33
*/

reject

调用失败,返回Promise.reject()  方法返回一个带有拒绝原因的 Promise 对象。
一般用来处理接口调用失败

Promise.reject(new Error('fail')).then(function() {
  // not called
}, function(error) {
  console.error(error); // Stacktrace
});

all

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例,回调的结果是一个数组。

示例:

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
  setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
  reject('reject');
});

// 等待所有 promise 完成,执行成功回调
Promise.all([p1, p2, p3, p4]).then(values => {
  console.log(values);
}, reason => {
  console.log(reason)
});

// 有失败请求,会直接结束,执行失败回调
Promise.all([p1, p2, p3, p4, p5]).then(values => {
  console.log(values);
}).catch(reason => {
  console.log(reason)
});

// logs
// reject
// [ 'one', 'two', 'three', 'four' ]

传入空的迭代对象,会立即结束执行

var p = Promise.all([]); // will be immediately resolved
var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
console.log(p);
console.log(p2)
setTimeout(function(){
    console.log('the stack is now empty');
    console.log(p2);
});

// logs
// Promise { <state>: "fulfilled", <value>: Array[0] }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }

从调用结果我们可以看出:

  1. 含有 p5 的请求,会走 .catch
  2. 存在失败回调会立即结束
  3. 传入空的迭代对象,代码执行是同步性的

特点总结:

  1. Promise.all 执行结束的标识是,所有都完成或第一个失败。
  2. Promise.all 接收的迭代对象为空时是同步的。

allSettled

大体上的使用跟 .all 类似,最大的不同的点是存在失败回调不会结束函数执行

看个例子:

Promise.allSettled([
  Promise.resolve(33),
  Promise.reject(new Error("an error")),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
]).then((values) => console.log(values));


// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'rejected', reason: Error: an error }
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
// ]

从调用结果我们可以看出:

  1. 存在失败,依旧会执行代码,直到所有 promise 调用完毕才结束
  2. 返回的数据是键值对数组,成功的值在 value,失败的值在 reason

any

Promise.any() 接收一个由 Promise 所组成的可迭代对象,该方法会返回一个新的 promise,只要有一个 promise 执行成功,该函数就结束。

示例:

执行第一个成功

const pErr = new Promise((resolve, reject) => {
  reject("总是失败");
});

const pSlow = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, "最终完成");
});

const pFast = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "很快完成");
});

Promise.any([pErr, pSlow, pFast]).then((value) => {
  console.log(value);
  // pFast fulfils first
})

// logs
// "很快完成"

没有成功回调

const pErr = new Promise((resolve, reject) => {
  reject('总是失败');
});

Promise.any([pErr]).catch((err) => {
  console.log(err);
})
// "AggregateError: No Promise in Promise.any was resolved"

从调用结果我们可以看出:

  1. 回调第一个成功的 promise,执行.then,结束调用
  2. 没有成功的 promise,执行.catch,结束调用

race

Promise.race(iterable)  方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

示例:

var p1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, "two");
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // 两个都完成,但 p2 更快
});

var p3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
    setTimeout(reject, 500, "four");
});

Promise.race([p3, p4]).then(function(value) {
  console.log(value); // "three"
  // p3 更快,所以它完成了
}, function(reason) {
  // 未被调用
});

var p5 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
    setTimeout(reject, 100, "six");
});

Promise.race([p5, p6]).then(function(value) {
  // 未被调用
}).catch(err=>{
     console.log(err); // "six"
  // p6 更快,所以它失败了
});

从调用结果我们可以看出: 只会回调一个结果,不管失败、成功,哪个快,回调哪个。