JavaScript 系列之异步编程(一)

1,121 阅读4分钟

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

一、Promise 基础

1.1 Promise 含义

Promise 的中文意思是承诺,也就是说,JavaScript 对你许下一个承诺,会在未来某个时刻兑现承诺。

1.2 Promise 状态

一个 Promise 对象值是未知的,状态是可变的,但是无论怎么变化,它的状态永远处于以下三种之间:

  • 进行中(pending)
  • 已经完成(fulfilled)
  • 拒绝(rejected)

Promise 的状态会发生变化,成功时会从 pending -> fulfilled,失败时会从 pending -> rejected,但是此过程是不可逆的,也就是不能从另外两个状态变成 pendingfulfilled/rejected 这两个状态也被称为 settled 状态。

1.3 Promise 意义

Promise的出现是为了解决 ES6 之前 JS 代码中频繁嵌套回调函数所导致的回调地狱问题,Promise 为 ES6 特性。

什么是回调地狱,来看一个最简单的示例:

setTimeout(() => {
  console.log(111);
  setTimeout(() => {
    console.log(222);
    setTimeout(() => {
      console.log(333);
      setTimeout(() => {
        console.log(444);
        // 你还可以放置更多
        ...
      }, 4000);
    }, 3000);
  }, 2000)
}, 1000);

那么 Promise 怎么解决回调地狱的:

// promise 解决
function f1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(111), 1000);
  }).then(data => console.log(data));
}
function f2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(222), 2000);
  }).then(data => console.log(data));;
}
function f3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(333), 3000);
  }).then(data => console.log(data));;
}
function f4() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(444), 4000);
  }).then(data => console.log(data));;
}
f1().then(f2).then(f3).then(f4);

1.4 Promise 使用

Promise 构造函数只有一个参数,该参数是一个函数,被称作执行器,执行器有 2 个参数,分别是 resolve()reject(),一个表示成功的回调,一个表示失败的回调。

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(5), 0)
}).then(v => console.log(v)) // 5

记住,Promise 实例只能通过 resolve 或者 reject 函数来返回,并且使用 then() 或者 catch() 获取,不能在 new Promise 里面直接 return,这样是获取不到 Promise 返回值的。

1. resolve(value)

Promise.resolve(5).then(v => console.log(v)) // 5

2. reject(value)

Promise.reject(5).catch(v => console.log(v)) // 5

3. 执行器错误通过 catch 捕捉

new Promise(function(resolve, reject) {
    if(true) {
       throw new Error('error!!')
    }
}).catch(v => console.log(v.message)) // error!!

1.5 Promise API

1.5.1 Promise.prototype.then(onFulfilled, onRejected)

image.png

可以看到,.then 里面拿到的是我们 Promise resolve 过后的数据。并且他还会返回一个 Promise 继续供我们调用,比如:

new Promise((resolve, reject) => {
  setTimeout(() => resolve(111), 1000);
}).then(data => {
  console.log(data); // 打印 111
  return data + 111; // 相当于 resolve(data + 111)
}).then(data => {
  console.log(data); // 打印 222
});

.then() 是可以一直链式调用的,因为它的返回值也是一个 Promise

1.5.2 Promise.prototype.catch(onRejected)

new Promise((resolve, reject) => {
  setTimeout(() => reject(111), 1000);
}).then(data => {
  console.log('then data:', data);
}).catch(e => {
  console.log('catch e: ', e);
});

image.png

1.5.3 Promise.prototype.finally(fn)

Promise 提供了标准结束方法 finally(),只要 Promise 状态变成 settled,无论是 rejected 还是 fulfilled,都会在 finally 里捕获。

image.png

finally 也会返回一个 promise,但是建议到 finally 就结束了。

具体的使用场景:

let isLoading = true;

fetch(myRequest).then((response) => {
  let contentType = response.headers.get("content-type");
  
  if (contentType && contentType.includes("application/json")) {
    return response.json();
  }
  
  throw new TypeError("Oops, we haven't got JSON!");
})
.then((json) => { console.error(json); })
.catch((error) => { console.error(error); })
.finally(() => { isLoading = false; });
  • 参数 fn 是一个无参函数,不论该 promise 最终是 fulfilled 还是 rejected。
  • finally 不会改变 promise 的状态。

1.5.4 Promise.all(iterable)

语法很简单:参数只有一个,可迭代对象,可以是数组,或者 Symbol 类型等。

Promise.all(iterable).then().catch()
Promise.all([
  new Promise(function(resolve, reject) {
    resolve(1)
  }),
  new Promise(function(resolve, reject) {
    resolve(2)
  }),
  new Promise(function(resolve, reject) {
    resolve(3)
  })
]).then(arr => {
  console.log(arr) // [1, 2, 3]
})

1. 接收一个 Promise 对象数组作为参数

image.png

2. 参数所有回调成功才是成功,返回值数组与参数顺序一致

image.png

可以看到,返回值是一个数组,并且每个元素对应的就是参数数组里对应过后的 resolve 值。

3. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息

image.png

可以看到,当把第二个和第三个分别设置成 reject 的时候,Promise.all 进入了 catch 也就是捕获异常阶段,捕获到的是第二个 reject 内容,也就是第一次出现的 reject 的那个地方。

一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了。