Promise入门

11/21/2020 Promise

# 文章目录

# 一、什么是 Promise?

Promise 是 ES6 引入的进行异步编程的新的解决方案。
备注:旧的是单纯的回调函数

从语法上来说:它就是一个构造函数,可以封装异步的任务,并且对结果进行处理。
从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功/失败 的结果值。

Promise最大的好处在于可以解决回调地狱的问题,并且它在指定回调与错误处理这块要更加的灵活与方便,而且Promise在现代项目当中,无论是 Web 还是 App 的项目当中都应用的十分广泛,无论是前端还是后端都可以看到它的身影,同时它也是现在面试的高频题目

# 二、Promise 初体验

# 2.1 在 nodejs 环境下读取文件内容

需求:读取当前目录下的 file 文件夹下的 content.txt 的内容并输出。

// 引入fs模块
const fs = require("fs");

// 回调函数形式
// fs.readFile('./file/content.txt', (err, data) => {
//   // 如果错误,则抛出错误
//   if(err) throw err

//   console.log(data.toString());
// })

// Promise新式
let p = new Promise((resolve, reject) => {
  fs.readFile("./file/content.txt", (err, data) => {
    if (err) reject(err);
    resolve(data);
  });
});

p.then(
  (value) => {
    console.log(value.toString());
  },
  (reason) => {
    console.log(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

output:

Hello Promise,This is Content
1

# 2.2 封装 AJAX

需求:使用 Promise 封装 AJAX 并读取接口数据并输出
接口地址: https://api.apiopen.top/getJoke

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 引入bootstrap 的样式 -->
  <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.css">
</head>
<body>
  <div class="container">
    <h2 class="page-header">Promise 封装 AJAX操作</h2>
    <button class="btn btn-primary" id="btn">点击发送AJAX</button>
  </div>
  <script>

    const btn = document.querySelector('#btn')
    btn.onclick = function () {
      const p = new Promise((resolve, reject) => {
        xhr = new XMLHttpRequest()
        xhr.open('GET', 'https://api.apiopen.top/getJoke')
        xhr.send()
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
              resolve(xhr.response)
            } else {
              reject(xhr.status)
            }
          }
        }
      })
      p.then(value => {
        console.log(value);
      }, reason => {
        console.warn(reason);
      })

    }
  </script>
</body>
</html>
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
39
40
41
42
43

# 三、怎么使用 Promise?

在了解怎么使用 Promise 之前,我们先来看看 Promise 实例对象的两个属性:
「PromiseState」和「PromiseResult」

# 3.1 Promise 的状态

每一个实例对象都拥有一个属性叫做 「PromiseState」,这个属性有三个值,分别是:

  • pending 「未决定的」
  • resolved / fullfilled 「成功」
  • rejected 「失败」

状态的改变

  1. pending 变为 resolved
  2. pending 变为 rejected

说明:只有这 2 种,且一个 promise 对象只能改变一次,改变后不可修改
无论变为成功还是失败,都会有一个结果数据
成功的结果数据一般称为 value,失败的结果数据一般称为 reason

# 3.2 Promise 的结果

每一个实例对象都拥有一个属性叫做 「PromiseResult」
它用于保存实例对象(异步任务) 「成功或失败」 的结果。

只有 resolvereject 可以修改该属性的值。

# 3.3 Promise 的基本流程

在这里插入图片描述

# 四、 Promise 的 API

# 4.1 resolve 方法

如果传入的参数为非 Promise 类型的对象,则返回的结果为成功 promise 对象
如果传入的参数为 Promise 对象,则参数的结果决定了 resolve 的结果

const p1 = Promise.resolve(520);
console.log(p1); // PromiseState => fulfilled

const p2 = Promise.resolve(
  new Promise((resolve, reject) => {
    reject("111");
  })
);

// 此处内部有一个失败的回调,但是没有处理.所以会产生报错
console.log(p2); // PromiseState => rejected   报错:Uncaught (in promise) 111

p2.catch((reason) => {
  console.log(reason); // 111
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4.2 reject 方法

永远返回一个 rejected「失败」的 Promise 对象。

// const p = Promise.reject(521)
// 注意此处会产生一个报错,原因是内部有一个失败的Promise但是没有对应的回调来处理它
// console.log(p); // PromiseState rejected

const p2 = Promise.reject(
  new Promise((resolve, reject) => {
    resolve("123");
  })
);
console.log(p2); // PromiseState rejected
p2.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason); // 失败的结果 是一个 Promise对象,这个Promise对象的状态为成功
  }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4.3 all 方法

语法:

Promise.all(Array) // Array 一组Promise对象
1

说明:返回一个新的 Promise,只有所有的 Promise 都成功才成功,只要有一个失败了就
直接失败

第一种情况,全部都是 resolved:

const p1 = new Promise((resolve, reject) => {
  resolve("ok");
});
const p2 = Promise.resolve("success");
const p3 = Promise.resolve("Oh Yeah");

const result = Promise.all([p1, p2, p3]);

console.log("result: ", result);
// PromiseState "fulfilled"
// PromiseResult ['ok','success','Oh Yeah']
1
2
3
4
5
6
7
8
9
10
11

第二种情况,有一个是 rejected:

const p1 = new Promise((resolve, reject) => {
  resolve("ok");
});
const p2 = Promise.reject("error info");
const p3 = Promise.resolve("Oh Yeah");

const result = Promise.all([p1, p2, p3]);

console.log("result: ", result);
// PromiseState "rejected"
// PromiseResult "error info"
1
2
3
4
5
6
7
8
9
10
11

# 4.4 race 方法

race 函数返回一个 Promise,它可以是完成( resolved),也可以是失败(rejected),这要取决于第一个完成的是哪个。

第一种情况,先完成的是 「P1 => resolved」 :

const p1 = new Promise((resolve, reject) => {
  resolve("ok");
});
const p2 = Promise.reject("error");
const p3 = Promise.resolve("Oh Yeah");

const result = Promise.race([p1, p2, p3]);
console.log(result); // PromiseState:"fulfilled"  PromiseResult:"ok"
1
2
3
4
5
6
7
8

第二种情况,先完成的是 「P2 => rejected」:

//第一钟测试方式:
//注意:这里改变的是race方法参数的位置
const p1 = new Promise((resolve, reject) => {
  resolve("ok");
});
const p2 = Promise.reject("error info");
const p3 = Promise.resolve("Oh Yeah");

const result = Promise.race([p2, p1, p3]);
console.log(result); // PromiseState:"rejected"  PromiseResult:"error info"

//第二钟测试方式:
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("ok");
  }, 1000);
});
const p2 = Promise.reject("error info");
const p3 = Promise.resolve("Oh Yeah");

const result = Promise.race([p1, p2, p3]);
console.log(result); // PromiseState:"rejected"  PromiseResult:"error info"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 4.5 实例对象的 then 方法

then 方法拥有返回值,返回值是一个 Promise 对象。状态是由回调函数的执行结果来决定.

  1. 返回结果是非 Primose 类型,状态为成功,返回值为 then 方法中的返回值(下方例子中的 123 即为返回值),方法中如果没有 return 则为 undefined
  2. 如果返回值是一个 Promise 对象, 那么内部 Promise 对象返回的状态和值决定外部 then 方法的状态和值
  3. 如果抛出错误,则 then 方法状态为 rejected,值为抛出的值

提示: then 方法的返回值与 async 修饰的函数的返回值一模一样

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

const result = p.then(
  (value) => {
    // 1.返回普通数据 状态为成功,没有return状态也为成功,值为undefined
    // return 123;

    // 2.抛出错误,状态为失败,值为抛出的值
    // throw '出错啦'

    // 3.返回一个 Promise ,内部的Promise决定外部then方法的状态及返回值
    return new Promise((resolve, reject) => {
      // resolve()
      // reject()
      throw "出错啦!";
    });
  },
  (err) => {
    console.error(err);
  }
);
console.log(result); // PromiseState: "rejected"  PromiseResult: "出错啦!"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

特殊情况:

then 方法期望的参数是一个函数,如果不是函数则会发生 Promise 穿透(值穿透),状态为上一个 Promise 的状态,值为上一个 Promise 的值。

// 特殊情况:
const p = new Promise((resolve, reject) => {
  resolve("ok");
  // reject('error')
});
const result = p.then(console.log(123));
console.log(result); // PromiseState: "fulfilled"  PromiseResult: "ok"
1
2
3
4
5
6
7

稍微加点难度:

Promise.resolve("foo")
  .then(Promise.resolve("bar"))
  .then(function(result) {
    console.log(result);
  });
1
2
3
4
5

当然,输出的结果为 foo。问其原因,答案如题——Promise 值穿透
解释:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

再来一道:

Promise.resolve(1)
  .then(function() {
    return 2;
  })
  .then(Promise.resolve(3))
  .then(console.log);
1
2
3
4
5
6

output: 2
解释:Promise.resolve(3) 不是函数,发生了值穿透

继续来:

Promise.resolve(1)
  .then(function() {
    return 2;
  })
  .then(function() {
    return Promise.resolve(3);
  })
  .then(console.log);
1
2
3
4
5
6
7
8

output:3

# 五、async 和 await

# 5.1 async

使用 async 关键字 修饰的函数 返回值永远为 Promise 对象,这个 Prmose 对象的状态与 then 方法返回值的状态都是应用的同一种规则。

一个最简单的例子:

async function main() {}
let result = main();
console.log(result); // "fulfilled" "undefined"
1
2
3

# 5.2 await

await 右侧的表达式一般为 promise 对象,但也可以是其它的

  1. 右侧没有 Promise 的情况: 直接将此值作为 await 的返回值
  2. 右侧为成功的 Promise: 返回的是 promise 成功的值
  3. 右侧为失败的 Promise: 抛出异常,需要使用 try catch 捕获处理

注意:
await 必须写在 async 函数中,但 async 函数中可以没有 await

async function main() {
  // 1.右侧没有Promise的情况:   直接将此值作为 await的返回值
  let result = await "str";
  console.log(result); // result => str  info => fulfilled  str
  return result;

  // 2.右侧为成功的 Promise:    返回的是 promise成功的值
  // let p1 = new Promise((resolve, reject) => {
  //   resolve('ok')
  // })
  // let result1 = await p1
  // console.log(result1); // result => ok  info => fulfilled  ok
  // return result1

  // 3.右侧为失败的 Promise:  抛出异常,需要使用 try catch 捕获处理
  // let p2 = new Promise((resolve, reject) => {
  //   reject('error')
  // })
  // try {
  //   let result2 = await p2
  // } catch (e) {
  //   console.log(e); // e => error  info => fulfilled  error
  //   return e
  // }
}
const info = main();
console.log(info);
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

# 六、async 与 await 结合发送 AJAX

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 引入bootstrap 的样式 -->
  <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.css">
</head>

<body>
  <div class="container">
    <h2 class="page-header">asyncawait封装AJAX操作</h2>
    <button class="btn btn-primary" id="btn">点击发送AJAX</button>
  </div>
  <script>
    function sendAJAX(url) {
      return new Promise((resolve, reject) => {
        xhr = new XMLHttpRequest()
        xhr.responseType = 'json'
        xhr.open('GET', url)
        xhr.send()
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
              resolve(xhr.response)
            } else {
              reject(xhr.status)
            }
          }
        }
      })
    }

    const btn = document.querySelector('#btn')
    btn.addEventListener('click', async function () {
      let result = await sendAJAX('https://api.apiopen.top/getJoke')
      console.log(result);
    })
  </script>
</body>
</html>
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
39
40
41
42
43
最后更新于: 2021年9月15日星期三晚上10点10分
Faster Than Light
Andreas Waldetoft / Mia Stegmar