前端技术

Promise 学习笔记

概述

Promise 是 JavaScript ES6 中处理异步操作的一种机制。它是一种表示异步操作最终完成或失败的对象。

异步操作的类型

  • fs 文件操作
  • 数据库操作
  • Ajax 网络请求
  • 定时器

为什么要使用 Promise

  • 指定回调函数的方式更加灵活
  • 支持链式调用,可以解决回调地狱问题

Promise 的特点

  • 链式调用(Chaining): 可以通过 .then() 方法连接多个处理异步操作的函数,形成链式调用,使代码更清晰。
  • 错误处理: 可以通过 .catch() 方法捕获 Promise 链中的任何错误,提供更方便的错误处理机制。
  • 状态不可逆: 一旦进入 Fulfilled 或 Rejected 状态,就不可逆转到其他状态。

Promise 初体验

定时器

生成一个点击按钮抽奖功能,2秒后弹出中奖结果,中奖概率为30%

回调函数形式

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
  setTimeout(() => {
    const n = rand(1, 100);
    if(n <= 30) {
      alert('恭喜中奖');
    } else {
      alert('再接再厉');
    }
  }, 2000);
});

promise形式:

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
  const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    const n = rand(1, 100);
    if(n <= 30) {
      resolve(); // 将 promise 对象的状态设置为成功
    } else {
      reject(); // 将 promise 对象的状态设置为失败
    }
  }, 2000);
  // then方法传递两个参数,分别表示成功执行的方法与失败执行的方法
  p.then(() => {
    alert('恭喜中奖');
  }, () => {
    alert('再接再厉');
  });
});

fs 文件操作

读取本地文件数据

回调函数形式

const fs = require('fs');
fs.readFile('./filename.txt',(err, data) => {
  if(err) throw err;
  console.log(data.toString());
});

promise形式:

const p = new Promise((resolve, reject) => {
  fs.readFile('./filename.txt', (err, data) => {
    if(err) reject(err);
    resolve(data);
  });
});

p.then(value => {
  console.log(value.toString());
}, reason => {
  console.log(reason);
});

Ajax 网络请求

点击按钮获取网络数据

传统形式

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://api.puji.design/getData');
  xhr.send();
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if(xhr.status >= 200 && xhr.status < 300) {
        console.log(xhr.response);
      } else {
        console.log(xhr.status);
      }
    }
  }
});

promise形式:

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://api.puji.design/getData');
    xhr.send();
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr.response);
        } else {
          reject(xhr.status);
        }
      }
    }
  });
  p.then(value => {
    resolve(value);
  }, reason => {
    resolve(reason);
  });
});

函数封装

function mineReadFile(path) {
  return new Promise((resolve, reject) => {
    require('fs').readFile(path, (err,data) => {
      if(err) reject(err);
      resolve(data);
    });
  });
}
mineReadFile('filename.txt').then(value => {
  console.log(value.toString());
}, reason => {
  console.log(reason);
});

Promise 对象的三种状态

  • Pending(进行中): 初始状态,表示操作尚未完成,处于进行中。
  • Resolved / Fulfilled(已成功): 操作成功完成,Promise 进入此状态,并返回操作的结果。
  • Rejected(已失败): 操作失败,Promise 进入此状态,并返回一个包含错误信息的对象。

Promise 对象的状态只能改变一次,无论变换为成功还是失败,都会有一个结果数据。

改变 Promise 状态

  • resolve() 如果当前是 pending 就会变为 resolved
  • reject() 如果当前是 pending 就会变为 rejected
  • 抛出异常 如果当前是 pending 就会变为 rejected
// pending => fulfilled
const p = new Promise((resolve, reject) => {
  resolve('ok');
});

// pending => rejected
const p = new Promise((resolve, reject) => {
  reject('error');
});

const p = new Promise((resolve, reject) => {
  throw 'error';
});

Promise 对象的值

实例对象中的另一个属性 PromiseResult,存放的是异步任务成功/失败的结果。可以通过 resolve 或 reject 函数对值进行修改。

API

Promise 构造函数

Promise (excutor) {}

  • excutor 函数: 执行器 (resolve, reject) => {};
  • resolve 函数: 内部定义成功时调用的函数;
  • reject 函数: 内部定义失败时调用的函数。

Promise.prototype.then

(onResolved, onRejected) => {}

  • onResolved 函数: 成功的回调函数 value => {}
  • onRejected 函数: 失败的回调函数 reason => {}

说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 Promise 对象。

Promise.prototype.catch

onRejected => {}

  • onRejected 函数: 失败的回调函数 reason => {}
const p = new Promise((resolve, reject) => {
  reject('error');
});
p.catch(reason => {
  console.log(reason);
});

方法

Promise.resolve()

返回一个成功/失败的 Promise 对象

  • 如果传入的参数为非 Promise 类型的对象(如数字,字符串等),则返回的结果为成功 Promise 对象
  • 如果传入的参数为 Promise 类型的对象,则参数的结果决定了 resolve 的结果
// 参数为非 Promise 类型的对象
const p1 = Promise.resolve(123);
console.log(p1);
// 返回 Promise 对象,状态 fulfilled,结果 123

// 参数为 Promise 类型的对象
const p2 = Promise.resolve(new Promise((resolve, reject) => {
  reject('error');
}));
p2.catch(reason => {
  console.log(reason);
});
// 返回 error

Promise.reject()

返回一个失败的 Promise 对象

Promise.all()

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

Promise.race()

返回一个新的 Promise,第一个完成的 promise 的结果状态就是最终的结果状态

常见问题

改变 Promise 状态与指定回调两者的先后顺序

  • 正常情况先指定回调再改变状态,但也可以先改变状态再指定回调
  • 如果先改状态再指定回调
    • 在执行器中直接调用 resolve()/reject()
    • 延迟更长时间再调用 then()
  • 什么时候得到数据
    • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
    • 如果先改变状态,那当是指定回调时,回调函数就会调用,得到数据

Promise.then()返回的新 Promise 的结果状态由什么决定

  • 由 then() 指定的回调函数执行的结果决定
const p = new Promise((resolve, reject) => {
  resolve('success');
});
const result = p.then(value => {
  ...
}, reason => {
  ...
});
console.log(result);
// 返回值是一个 Promise 对象

如何串联多个操作任务

  • Promise 的 then() 返回一个新的 Promise,可以开成 then() 的链式调用
  • 通过 then 的链式调用串联起多个同步/异步任务
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  },1000);
});
p.then(value => {
  return new Promise((resolve, reject) => {
    resolve(2);
  });
}).then(value => {
  console.log(value);
});
// 返回值 2

Promise 的异常穿透

  • 当使用 Promise 的 then 链式调用时,可以在最后指定失败的回调
  • 前面任何操作出了异常,都会传达到最后失败的回调中处理

中断 Promise 链

当使用 Promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数

办法:在回调函数中返回一个 pendding 状态的 promise 对象

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Success');
  }, 1000);
});
p.then(value=> {
  console.log(111);
  return new Promise(() => {});
  // 后续的函数都不会执行了
}).then(value=> {
  console.log(111);
}).then(value=> {
  console.log(111);
}).catch(reason=> {
  console.warn(reason);
})

async 与 await

async 函数

  • 函数的返回值为 Promise 对象
  • Promise 对象的结果由 async 函数执行的返回值决定

await 表达式

  • await 右侧的表达式一般为 Promise 对象,但也可以是其他的值
  • 如果表达式是 Promise 对象,await 返回的是 Promise 成功的值
  • 如果表达式是其他值,直接将此值作为 await 的返回值
async function main() {
  const p = new Promise((resolve, reject) => {
    reject('Error');
  });
  try {
    let res = await p;
  } catch(e) {
    console.log(e);
  }
}

参考链接

https://www.bilibili.com/video/BV1GA411x7z1

内容目录

PUJI Design 朴及设计 (c) 2024. 沪ICP备17052229号