目录

🚊 Promise 的基本使用

# 理解 Promise

从抽象角度看:

  • Promise 是一门 ES6 规范下新的技术;

  • Promise 是 JavaScript 中进行异步编程的新解决方案

从具体角度看:

  • 从语法上来讲: Promise 是一个构造函数;
  • 从功能上来讲: Promise 对象用来封装一个异步操作并且可以获取其成功 / 失败的结果值。

理解例子:

假如你是一名歌手,你向粉丝承诺会在(单曲发布)的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,(单曲发布成功)让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,(单曲发布失败)他们也能及时收到相关通知。

# Promis 语法

Promise 的构造器语法:

let promise = new Promise(function(resolve, reject) {
  // executor
});
1
2
3

new Promise 被创建,executor 会自动运行。它包含最终应产出结果的生产者代码。它的参数 resolvereject 是由 JavaScript 自身提供的回调。要执行的代码仅在 executor 的内部。

当 executor 获得了结果,无论是早还是晚都没关系,它应该调用以下回调之一:

  • resolve(value) :如果任务成功完成并带有结果 value
  • reject(error) :如果出现了错误, error 为错误对象。

简而言之:executor 会自动运行并尝试执行一项工作。尝试结束后,如果成功则调用 resolve ,如果出现错误则调用 reject

# Promise 的简单使用案例

🌰 例子 / 定时器:

const btn = document.querySelector('#btn');
// 绑定单击事件
btn.addEventListener('click', function () {
    /*setTimeout(() => {
        // 获取从一到一百的随机数
        let n = rand(1, 100);
        // 判断
        if (n <= 30) {
            alert('中奖')
        } else {
            alert('再接再厉')
        }
    }, 1000)*/

    // Promise 实现
    const p = new Promise((resolve, reject) => {
        // 成功时调用resolve,失败时调用reject(函数类型)
        //包含一个函数
        setTimeout(() => {
            // 获取从一到一百的随机数
            let n = rand(1, 100);
            // 判断
            if (n <= 30) {
                resolve(n) // 将Promise对象状态设置为「成功」
            } else {
                reject(n) // 将Promise对象状态设置为「失败」
            }
        }, 1000)
    })

    // 第一个是成功时调用,第二个是失败时调用
    // 在构造p时传给状态的值,不同状态分别为 value / reason
    p.then((value) => {
        alert('成功中奖,中奖数字为' + value)
    }, (reason) => {
        alert('再接再厉,当前号码为'+ reason)
    })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

🌰 例子 / 前文 加载脚本 例子:

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));

    document.head.append(script);
  });
}
1
2
3
4
5
6
7
8
9
10
11

在加载脚本完成后返回一个新的 Promise 对象。所以在外部可以使用 处理程序 处理 成功或者失败时的结果。

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
	script => console.log(`${script.src} is loaded`),
  error => console.log(`Error: ${error.massage}`)
)

promise.then(
	script => console.log("... another handler")
)
1
2
3
4
5
6
7
8
9
10

# 对比 Promise 与 回调模式

Promis Callbacks
Promises 允许按照自然顺序进行编码 在调用要执行的函数 之前,必须知道如何处理结果(设定好回调)。
可以根据需要,可以有多个处理结果的函数。(Promise 链) 只能有一个回调。

# Promise 的状态

Promise 状态(PromiseState)是 Promise 实例对象的一个属性,有三个值: pending (初始状态)、 resolved / fulfilledrejected

从上面简单使用 Promise 的例子中,可以看到 Promise 可以有两种转变:

  • pending ➡️ resolved
  • pending ➡️ rejected

注意,Promise 只有这两种状态,并且每一个 Promise 对象 只能转变一次,无论转为成功还是失败,都会有一个与状态对应的结果:

# Promise 的结果

resolve(value) 被调用时(成功状态 fulfilled )结果为 value ;在 reject(error) 被调用时(失败状态 rejected ) 结果为 error 。( resolverejecet 只能有一个参数)

image-20220515105642046

提示

  • resolve / reject 可以立即进行。虽然通常 excutor 是异步执行某些操作,并在一段时间(经过判断)之后调用 resolvereject ,但这不是必须的。也可以立即调用 resolvereject

    let promise = new Promise(function(resolve, reject) {
      // 不花时间去做这项工作
      resolve(123); // 立即给出结果:123
    });
    
    1
    2
    3
    4
  • 状态和结果( stateresult是内部的。无法直接访问它们。但可以对它们使用 .then / .catch / .finally 方法。

# .then / .catch / .finally

Promise 对象中是 执行函数(生产者代码 / 歌手) 与 消费函数(粉丝)之间的连接。消费函数将接收 结果(成果或者出错),可以通过使用 .then.catch.finally 为消费函数注册。

# .then

promise.then{
  // 处理成功的结果的消费函数
	function(result) { /* ... */ }
 
  // 处理出错结果的消费函数
	function(error) { /* ... */ }
}
1
2
3
4
5
6
7

🌰 例子:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 运行 .then 中的第一个函数
promise.then(
  result => alert(result), // 1 秒后显示 "done!"
  error => alert(error) // 不运行
);
1
2
3
4
5
6
7
8
9

当回调中出错时:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// reject 运行 .then 中的第二个函数
promise.then(
  result => alert(result), // 不运行
  error => alert(error) // 1 秒后显示 "Error: Whoops!"
);
1
2
3
4
5
6
7
8
9

一般 .then 中可以只放处理成功时的结果的函数:

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then( result => alert(result) );
// 简化为
promise.then(alert)
1
2
3
4
5
6
7

# .catch

.catch 一般用于处理出错的结果 error

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

promise.catch(error => alert(error))
// 简化为:
// .catch(error) 与 promise.then(null, error) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
1
2
3
4
5
6
7
8

(相当于 .then(null, error)

# .finally

就像常规 try {...} catch {...} 中的 finally 子句一样,Promise 中也有 finally

.finally(f) 调用与 .then(f, f) 类似,在某种意义上, f 总是在 Promise 状态被确定时运行:即 Promise 被 resolve 或 reject。

finally 是执行清理(cleanup)的很好的处理程序(handler),例如无论结果如何,都停止使用不再需要的加载指示符(indicator)。

🌰 例子:

new Promise((resolve, reject) => {
  /* ... */
})
  // 在 promise 为 settled 时运行,无论成功与否
  .finally(() => stop loading indicator)
  // 所以,加载指示器(loading indicator)始终会在我们处理结果/错误之前停止
  .then(result => show result, err => show error)
1
2
3
4
5
6
7

提示

.finally.then 的区别:

  • finally 处理程序(handler)没有参数。在 finally 中,不必知道 Promise 成功出错与否, finally 执行的是常规的 定稿程序(finalizinig procedures)。

  • finally 将结果和出错(value 和 error) 传递给下一个处理程序:

    new Promise((resolve, reject) => {
      setTimeout(() => resolve("result"), 2000)
    })
      .finally(() => alert("Promise ready"))
      .then(result => alert(result)); // <-- .then 对结果进行处理
    
    1
    2
    3
    4
    5
    new Promise((resolve, reject) => {
      throw new Error("error");
    })
      .finally(() => alert("Promise ready"))
      .catch(err => alert(err));  // <-- .catch 对 error 对象进行处理
    
    1
    2
    3
    4
    5

    因为 finally 并不是意味着要处理 Promise 的结果,所以它将结果传递了下去。

.then / .catch / .finally 三者为 Promise 状态确定后附加的处理程序。如果 Promise 为 pending 状态,这三种处理程序(handler)将等待它;否则,如果 Promise 已经是 settled 状态,它们就会运行。

这给 Promise 带来更加灵活的特性。因为可以随时添加处理程序,如果结果已经存在了,它们就会执行。

# 总结

# Promise 的基本流程

分析上述基本使用例子,得出 Promise 的基本流程:

image-20220420231156529

# Promise 的特点

  • 指定回调函数的方式更加灵活

    • 在以前,回调函数必须在启动异步任务前指定

    • Promise 下的流程为:启动异步任务 ➡️ 返回 Promise 对象 ➡️ 给 Promise 对象绑定回调函数 (甚至可以在异步任务将结束后指定多个

  • 支持链式调用,可以解决「回调地狱」的问题:

    • 「回调地狱」:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件。回调地狱带来了 代码不便于阅读、不便与异常处理的缺点。使用 Promise 链式调用解决。

    • 终极的解决方案: async / await

📢 上次更新: 2022/09/02, 10:18:16