Promise使用详细说明、Promise的细节补充、Promise的常见类方法

导读:本篇文章讲解 Promise使用详细说明、Promise的细节补充、Promise的常见类方法,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.异步代码的困境

在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise

  • 虽然等你学会Promise之后,会觉得Promise不过如此;
  • 但是在初次接触的时候都会觉得这个东西不好理解;

那么这里我从一个实际的例子来作为切入点:

  • 我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟网络请求);

  • 如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去

  • 如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息

    // 第二个参数和第三个参数分别是成功的回调和失败的回调
    function execCode(counter, successCallback, failureCallback) {
      // 异步任务
      setTimeout(() => {
        console.log("正在网络请求");
    
        if (counter > 0) {
          let total = 0;
          for (let i = 0; i < counter; i++) {
            total += i;
          }
    
          // 网络请求成功的回调
          successCallback(total);
        } else {
          // 网络请求失败的回调 
          failureCallback(`${counter}这个值有问题`);
        }
      }, 3000);
    }
    
    execCode(
      100,
      (value) => {
        console.log("网络请求成功, 请求结果:", value);
      },
      (err) => {
        console.log("网络请求失败, 失败原因:", err);
      }
    ); // 网络请求成功, 请求结果: 4950
    
    execCode(
      0,
      (value) => {
        console.log("网络请求成功, 请求结果:", value);
      },
      (err) => {
        console.log("网络请求失败, 失败原因:", err);
      }
    ); // 网络请求失败, 失败原因: 0这个值有问题
    
  • 在ES6之前, 封装网络请求都是类似于上面代码的封装

  • 但是我们发现有一个弊端, 在实际开发中设计网络请求函数的人和调用函数的人并不是同一个, 因此调用者必须小心翼翼, 要清楚的知道设计者是怎样设计的, 要求调用者如何传入参数, 才能放心调用, 这也是我们ES6之前的异步代码的困境

2.Promise开发中基本使用

2.1 什么是Promise

在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:

  • 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
  • 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它
    这个函数到底怎么用;

我们来看一下Promise的API是怎么样的:

  • Promise是一个类,可以翻译成 承诺、许诺 、期约
  • 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象
  • 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
    1. 这个回调函数会被立即执行并且给传入另外两个回调函数resolve、reject
    2. 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
    3. 当我们调用reject回调函数时会执行Promise对象的catch方法传入的回调函数;

2.2 Promise代码结构

上面说起来有些抽象, 我们来看一下Promise代码结构:

const promise = new Promise((resolve, reject) => {
  // 调用resolve, 执行then传入的回调
  resolve("成功结果")
  // 调用reject, 执行catch传入的回调
  reject("错误信息")
})

promise.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

上面Promise使用过程,我们可以将它划分成三个状态:

  1. 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;
    • 当执行executor中的代码时,处于该状态;
  2. 已兑现(fulfilled): 意味着操作成功完成;
    • 执行了resolve时,处于该状态,Promise已经被兑现;
  3. 已拒绝(rejected): 意味着操作失败;
    • 执行了reject时,处于该状态,Promise已经被拒绝;

2.3 Promise的Executor

Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:

new Promise((resolve, reject) => {
	console.log("executor代码")
})

通常我们会在Executor中确定我们的Promise状态:

  • 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved)
  • 通过reject,可以拒绝(rejected)Promise的状态

这里需要注意:一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的

  • 在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成 兑现(fulfilled)
  • 在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);
const promise = new Promise((resolve, reject) => {
  resolve();
  // 由于promise的状态已经确定为 已兑现, 状态确定就不会改变, 后面reject回调改变状态代码无效
  reject();
});

promise.then((res) => {
	console.log(res);
}).catch((err) => {
	console.log(err);
});

2.4 Promise重构请求

知道了Promise的结构, 我们可以将上面的案例使用Promise重构:

function execCode(counter) {
  // 通过new创建Promise对象时, 我们需要传入一个回调函数
  // 1.这个回调函数会被立即执行, 并且传入另外两个回调resolve和reject
  const promise = new Promise((resolve, reject) => {
    // 网络请求的代码
    setTimeout(() => {
      if (counter > 0) {
        let total = 0;
        for (let i = 0; i < counter; i++) {
          total += i;
        }
        // 2.网络请求成功调用resolve回调函数时,会执行Promise对象的then方法传入的回调
        resolve(total);
      } else {
        // 3.网络请求失败调用reject回调函数时, 会执行Promise对象的catch方法传入的回调
        reject(`${counter}这个值有问题`);
      }
    }, 3000);
  });

  return promise;
}


// 1.测试请求成功
const promise1 = execCode(100);
// 请求成功执行then中的回调函数
promise1.then(value => {
  console.log("网络请求成功, 请求结果:", value);
});
// 请求失败执行catch中的回调函数
promise1.catch(err => {
  console.log("网络请求失败, 失败原因:", err);
});


// 2.测试请求失败
const promise2 = execCode(0);
// 请求成功执行then中的回调函数
promise2.then(value => {
  console.log("网络请求成功, 请求结果:", value);
});
// 请求失败执行catch中的回调函数
promise2.catch(err => {
  console.log("网络请求失败, 失败原因:", err);
});


// 3.我们可以将上面代码合并起来简写
const promise3 = execCode(10)
promise3.then(value => {
  console.log("网络请求成功, 请求结果:", value);
}).catch(err => {
  console.log("网络请求失败, 失败原因:", err);
})

掌握以上promise的使用方法, 开发中promise已经基本没有问题, 接下来的知识理解即可, 在开发中很少用到

2.5 resolve不同值的区别

情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;

const promise = new Promise((resolve, reject) => {
  resolve([
    { name: "iphone", price: 5000 },
    { name: "xiaomi", price: 3000 },
  ]);
});

promise.then((res) => {
  console.log(res); // [{ name: "iphone", price: 5000 },{ name: "xiaomi", price: 3000 }]
});

情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态, 并且新的Promise会等待原Promise的决议的结果作为自己决议的结果:(了解, 开发中基本不会这样写)

const p = new Promise((resolve) => {
  setTimeout(() => {
    console.log("p的resolve");
  }, 2000);
});

const promise = new Promise((resolve, reject) => {
  resolve(p);
});

promise.then((res) => {
  console.log(res); // p的resolve
});

情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结
果来决定Promise的状态:
(了解, 开发中基本不会这样写)

const promise = new Promise((resolve, reject) => {
  resolve({
    name: "kaisa",
    then: function (resolve) {
      resolve(1111);
    },
  });
});

promise.then((res) => {
  console.log(res); // 1111
});

3.Promise的细节补充

3.1 then方法使用的补充

3.1.1 then方法接收两个参数

then方法是Promise对象上的一个方法(实例方法):

  • 它其实是放在Promise的原型上的 Promise.prototype.then

then方法可以接受两个参数:

  • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
  • rejected的回调函数:当状态变成reject时会回调的函数;
const promise = new Promise((resolve, reject) => {
  // resolve("成功信息");
  reject("失败信息");
});

promise.then(res => {
    console.log("成功的回调", res);
  },err => {
    console.log("失败的回调", err);
  }
);

上面这种写法也是可以的, 但是我们更推荐下面这种写法

const promise = new Promise((resolve, reject) => {
  // resolve("成功信息");
  reject("失败信息");
});

promise.then(res => {
    console.log("成功的回调", res);
  },err => {
    console.log("失败的回调", err);
  }
);

3.1.2 then方法多次调用

一个Promise的then方法是可以被多次调用的:

  • 每次调用我们都可以传入对应的fulfilled回调;
  • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行
const promise = new Promise((resolve, reject) => {
  resolve("成功信息");
});

promise.then((res) => {
  console.log("成功的回调", res); // 成功的回调 成功信息
});

promise.then((res) => {
  console.log("成功的回调", res); // 成功的回调 成功信息
});

promise.then((res) => {
  console.log("成功的回调", res); // 成功的回调 成功信息
});

promise.then((res) => {
  console.log("成功的回调", res); // 成功的回调 成功信息
});

3.1.3 then方法的返回值

then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用:

 const promise = new Promise((resolve, reject) => {
   resolve("aaa");
 });
 
 // 链式调用
 promise.then().then().then().then();

但是then方法返回的Promise到底处于什么样的状态呢

Promise有三种状态,那么这个then方法返回的Promise处于什么状态呢?

当then方法中的回调函数本身在执行的时候,那么它处于等待(pending)状态

当then方法中的回调函数返回一个结果时,那么它处于决议状态,并且会将回调函数的结果作为resolve的参数

  1. 情况一:回调函数返回的是一个普通的值;

    then方法是返回一个新的Promise, 这个新的Promise的兑现(决议)是等到then方法传入的回调函数有返回值时, 将then方法中的回调函数的返回值, 作为新的Promise的兑现(决议)的结果

    const promise = new Promise((resolve, reject) => {
      resolve("aaa");
    });
    
    promise
      .then((res) => {
        console.log("第一个then方法:", res); // 第一个then方法: aaa
        return "bbb";
      })
      .then((res) => {
        console.log("第二个then方法:", res); // 第二个then方法: bbb
      })
    

    上面代码中, 它并不是直接将函数返回值作为下一个then方法的res, 我们可以将then方法中的回调函数看做fn, 我们简单理解一下它内部的原理

    // 上面代码中return相当于执行下面的操作
    // 我们将第一个then方法的回调函数看做一个fn
    // then方法的返回值是一个Promise, 第一个then方法相当于内部创建了一个新的Promise
    new Promise((resolve, reject) => {
      // 这个创建出来的新Promise内部会将上一个then方法中的回调函数fn的返回值保存
      const result = fn()
      // 并将这个返回值作为这个新的Promise的决议(兑现)结果
      resolve(result)
    })
    // 因此我们第二个then方法回调函数中的res拿到的就是新的Promise的result
    
  2. 情况二:返回一个Promise;

    如果第一个then方法的回调函数, 返回值是一个Promise会怎样执行呢 ?

    1. 第一个then方法返回值同样是一个新的Promise, 这个新的Promise的resolve中传入的又是newPromise这个Promise
    2. 新的Promise会等待newPromise的决议结果, 并将newPromise的决议结果作为新的Promise的决议结果
    3. 因此**第二个then方法回调函数中的res其实是newPromise的决议结果
  3. 情况三:返回一个thenable值;

     const promise = new Promise((resolve, reject) => {
       resolve("aaa");
     });
     
     const newPromise = new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve("kaisa");
       }, 3000);
     });
     
     promise
       .then((res) => {
         console.log("第一个then方法:", res); // 第一个then方法: aaa
       	// 让回调函数返回一个Promise
         return newPromise;
       })
       .then((res) => {
         console.log("第二个then方法:", res); // 第二个then方法: kaisa
       });
    

    then方法中回调函数返回的是一个含有then方法的对象

    const promise = new Promise((resolve, reject) => {
      resolve("aaa");
    });
    
    promise
      .then((res) => {
        console.log("第一个then方法:", res); // 第一个then方法: aaa
        // 返回一个then方法的对象
        return {
          then: function (resolve) {
            resolve("thenablel");
          },
        };
      })
      .then((res) => {
        console.log("第二个then方法:", res); // 第二个then方法: thenablel
      });
    

当then方法抛出一个异常时,那么它处于reject状态, 下面会演示;

3.2 catch方法使用的补充

3.2.1 catch方法多次调用

catch方法也是Promise对象上的一个方法(实例方法):

  • 它也是放在Promise的原型上的 Promise.prototype.catch

一个Promise的catch方法是可以被多次调用的:

  • 每次调用我们都可以传入对应的reject回调;
  • 当Promise的状态变成reject的时候,这些回调函数都会被执行;
const promise = new Promise((resolve, reject) => {
  reject("访问失败");
});

promise.catch((err) => {
  console.log("err:", err); // err:访问失败
});

promise.catch((err) => {
  console.log("err:", err); // err:访问失败
});

promise.catch((err) => {
  console.log("err:", err); // err:访问失败
});

promise.catch((err) => {
  console.log("err:", err); // err:访问失败
});

3.2.2 catch方法的返回值

事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:

  • reject方法会调用最近的一个catch方法, 即使不是同一个Promise, 例如下面代码, 第一个promise中的reject找到的并不是本身的catch方法, 而是第三个then方法返回的Promise中的catch方法
const promise = new Promise((resolve, reject) => {
  reject("错误信息")
})

promise.then(res => {
  console.log("第一个then的回调", res)
}).then(res => {
  console.log("第二个then的回调", res)
}).then(res => {
  console.log("第三个then的回调", res)
}).catch(err => {
  console.log("catch回调被执行", err) // catch回调被执行 错误信息
})
  • 上面代码中, 如果我们想第二个或者第三个then方法返回的Promise中, 执行reject, 因为then方法中没有reject方法, 那需要怎么做呢?
  • 这个时候我就需要抛出一个异常
const promise = new Promise((resolve, reject) => {
  resolve("aaa")
})

promise.then(res => {
  console.log("第一个then的回调", res)

  // 抛出异常 可以执行then方法返回的Promise的reject
  // throw new Error("第二个Promise的异常")

  return "bbb"
}).then(res => {
  console.log("第二个then的回调", res)

  // 抛出异常 可以执行then方法返回的Promise的reject
  throw new Error("第三个Promise的异常")

  return "ccc"
}).catch(err => {
  console.log("catch回调被执行", err) // catch回调被执行 Error: 第二个Promise的异常 at index.html:186:17
})

小结: 总的来说, catch并不是某一个Promise被拒绝时才会回调, 例如上面代码中, 不管是哪一个Promise被拒绝, 都会回调catch

4.finally方法

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行
的代码。

finally方法是不接收参数的,因为无论前面是已决议 (fulfilled) 状态,还是已拒绝 (rejected) 状态,它都会执行。

const promise = new Promise((resolve, reject) => {
  resolve();
  // reject()
});

promise.then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
}).finally(() => {
// 不管是什么状态都会执行finally方法中的代码
  console.log("哈哈哈哈");
  console.log("呵呵呵呵");
});

5.Promise的类方法

前面我们学习的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的

  • 下面我们再来学习一下Promise的类方法

5.1 resolve方法

有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成

  • Promise.resolve的用法相当于new Promise,并且执行resolve操作
Promise.resolve("coder")
// 等价于
const promise = new Promise((resolve) => resolve("coder"))

resolve参数的形态

  • 情况一:参数是一个普通的值或者对象
  • 情况二:参数本身是Promise
  • 情况三:参数是一个thenable

5.2 reject方法

reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。

  • Promise.reject的用法相当于new Promise,只是会调用reject
Promise.reject("error")
// 等价于
const promise = new Promise((resolve, reject) => {
  reject("error")
})

注意: Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。

5.3 all方法

另外一个类方法是Promise.all:

  • 它的作用是将多个Promise包裹在一起形成一个新的Promise

  • 新的Promise状态由包裹的所有Promise共同决定

    1. 所有的Promise状态变成已兑现 (fulfilled) 状态时新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组

      // 创建三个Promise
      const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("p1 resolve")
        }, 3000)
      })
      
      const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("p2 resolve")
        }, 2000)
      })
      
      const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("p3 resolve")
        }, 5000)
      })
      
      // 会等待五秒, 当所有Promise都有结果才会打印
      Promise.all([p1, p2, p3]).then(res => {
        console.log("all Promise res:", res) // all Promise res: ['p1 resolve', 'p2 resolve', 'p3 resolve']
      })
      
    2. 有一个Promise状态为reject时新的Promise状态为reject,并且会将第一个reject的返回值作为参数

      // 创建三个Promise
      const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          // resolve("p1 resolve")
          reject("p1 reject")
        }, 3000)
      })
      
      const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          // resolve("p2 resolve")
          reject("p2 reject")
        }, 2000)
      })
      
      const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("p3 resolve")
        }, 5000)
      })
      
      // p1 p2都有返回reject, 会将第一个reject的返回值, 也就是p2的返回值作为参数
      Promise.all([p1, p2, p3]).then(res => {
        console.log("all Promise res:", res)
      }).catch(err => {
        console.log("all Promise err:", err) // all Promise err: p2 reject
      })
      

应用场景: 在开发中, 假如我们有三个网络请求, 我们想要三个网络请求都有结果后, 一起返回给我们就可以使用Promise.all方法

5.4 allSettled方法

all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。

  • 那么对于已决议(resolved)状态的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的

在ES11(ES2020)中,添加了新的API Promise.allSettled:

  • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态
  • 并且这个Promise的结果一定是fulfilled的
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("p1 resolve")
    reject("p1 reject")
  }, 3000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("p2 resolve")
    reject("p2 reject")
  }, 2000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3 resolve")
  }, 5000)
})

// allSettled方法
Promise.allSettled([p1, p2, p3]).then(res => {
  console.log("all allSettled:", res)   
})

我们来看一下打印结果:

all allSettled: [
  {status: 'rejected', reason: 'p1 reject'}, 
  {status: 'rejected', reason: 'p2 reject'}, 
  {status: 'fulfilled', value: 'p3 resolve'}
]
  • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的
  • 这个对象中包含status状态,以及对应的value值

5.5 race方法

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:

  • race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("p1 resolve")
    reject("p1 reject")
  }, 3000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("p2 resolve")
    reject("p2 reject")
  }, 2000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3 resolve")
  }, 5000)
})

// 由于p2最先返回一个结果 并且是已拒绝状态 那么会执行catch的回调
Promise.race([p1, p2, p3]).then(res => {
  console.log("race res:", res)
}).catch(err => {
  console.log("race err:", err) // race err: p2 reject
})

5.6 any方法

any方法是ES12中新增的方法,和race方法是类似的:

  • any方法会等到一个fulfilled状态,才会决定新Promise的状态;
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1 resolve")
  }, 3000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("p2 resolve")
    reject("p2 reject")
  }, 2000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3 resolve")
  }, 5000)
})

// 最先返回的p2 状态是已拒绝, 那么便会等下一个是p1 状态是以决议 就会返回p2
Promise.any([p1, p2, p3]).then(res => {
  console.log("any promise res:", res) // any promise res: p1 resolve
})
  • 如果所有的Promise都是rejected的,那么也会等到所有的Promise都变成rejected状态, 再执行catch中的回调
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p1 reject")
  }, 3000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p2 reject")
  }, 2000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p3 reject")
  }, 5000)
})

// 当所有Promise的都是rejected时
Promise.any([p1, p2, p3]).then(res => {
  console.log("any promise res:", res)
}).catch(err => {
  console.log("any promise err:", err) // any promise err: AggregateError: All promises were rejected
})

如果所有的Promise都是reject的,那么会报一个AggregateError的错误。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120131.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!