axios原理之promise

axios 是基于 promise 的 HTTP 库,支持 promise 的所有 API,9.1 节和 9.2 节只讲解了怎样使用 axios,本节讲解 promise,掌握 promise 更有利于我们理解 axios。

什么是promise

通俗地讲,promise 是 JS 中解决异步编程的语法,从语法上讲,promise 是一个构造函数。从功能上讲,用 promise 对象来封装异步操作并获取结果。

为什么要用promise

promise 支持链式调用,可以解决回调地狱。

什么是回调地狱?

回调地狱涉及多个异步操作,多个回调函数嵌套使用。例如有3个异步操作,第2个异步操作是以第1个异步操作的结果为条件的,同理第3个异步操作是以第2个异步操作的结果为条件的。

此时出现了回调函数嵌套,代码将向右侧延伸,不便于阅读,也不便于异常处理,使用 promise 首先能解决回调函数嵌套问题,代码从上往下执行,更有利于代码阅读及代码异常处理。

promise基本使用

首先需要创建 promise 对象并传入回调函数,promise 的基本使用代码如下。

const p = new Promise((resolve, reject) => {
  //执行异步操作
  setTimeout(() => {
    const time = Date.now();
    //当前时间为偶数代表成功,当前时间为奇数代表失败
    if (time % 2 == 0) {
//执行成功调用resolve(value)
      resolve('成功的数据:' + time);
    } else {
//执行失败调用reject(reason)
      reject('失败的数据:' + time);
    }
  }, 1000);
});

p.then(
  value => {
//接收成功的value数据
    console.log('接收成功的数据---' + value);
  },
  reason => {
//接收失败的reason数据
    console.log('接收失败的数据---' + reason);
  }
);

代码解析如下。

(1)new Promise()实例对象要传入回调函数,回调函数有两个形参,分别是resolve和reject。 (2)在回调函数中执行异步操作,在此案例中是获取当前时间,如果当前时间为偶数,表示异步操作执行成功,如果当前时间为奇数,表示异步操作执行失败。 (3)如果当前时间为偶数,则调用成功的resolve()方法,方法中的参数就是需要展示的数据。 (4)如果当前时间为奇数,则调用失败的reject()方法,方法中的参数就是需要展示的数据。 (5)使用.then()方法获取成功或失败的数据,如果获取数据成功,则调用第一个回调函数,如果获取数据失败,则调用第二个回调函数,最终的运行结果如图9-2所示。

image 2025 02 11 17 07 45 777
Figure 1. 图9-2 promise执行结果

promise的API

下面介绍 promise 的常用 API。

Promise (excutor) {}

(1)excutor()函数:同步执行(resolve, reject) ⇒ {}。 (2)resolve()函数:定义成功时调用的函数value ⇒ {}。 (3)reject()函数:定义失败时调用的函数reason ⇒ {}。

executor 会在 promise 内部立即同步回调,异步操作则在执行器中执行。

Promise.prototype.then()方法:(onResolved, onRejected) ⇒ {}

(1)onResolved()函数:成功的回调函数为(value) ⇒ {}。 (2)onRejected()函数:失败的回调函数为(reason) ⇒ {}。

Promise.prototype.catch()方法:(onRejected) ⇒ {}

onRejected() 函数:失败的回调函数为(reason) ⇒ {}。 代码展示如下。

new Promise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    // 异步操作成功
    resolve('成功的数据');
    // 异步操作失败
    reject('失败的数据');
  }, 1000);
}).then(
  value => {
    // 获取成功的数据
    console.log(value);
  }
).catch(
  reason => {
    // 获取失败的数据
    console.log(reason);
  }
);

Promise.resolve()方法:(value) ⇒ {}

value:成功的数据。

案例:定义成功值为1的promise对象。

1)方法一

const p1 = new Promise((resolve, reject) => {
  resolve(1);
});

p1.then(value => {
  console.log(value);
});

2)方法二

const p2 = Promise.resolve(1);

p2.then(value => {
  console.log(value);
});

Promise.resolve 可以更加简洁、方便地定义成功的 promise 对象。

Promise.reject()方法:(reason) ⇒ {}

reason:失败的原因。

案例:定义失败值为 2 的 promise 对象。

1)方法一

const p3 = new Promise((resolve, reject) => {
  reject(2);
});

p3.then(
  null,
  reason => {
    console.log(reason);
  }
);

2)方法二

const p4 = Promise.reject(2);

p4.then(null, reason => {
  console.log(reason);
});

.then() 方法中,第一个是获取成功的回调函数,第二个是获取失败的回调函数,由于本案例是获取失败的数据,所以第一个回调函数使用 null

或者使用 .catch() 方法获取,代码如下。

const p4 = Promise.reject(2);

p4.catch(reason => {
  console.log(reason);
});

Promise.all()方法:(promises) ⇒ {}

promises:多个 promise 数组。

作用:返回一个新的 promise 对象,当数组中的所有 promise 对象都执行成功,才为成功,只要数组中有一个 promise 对象执行失败,就为失败,代码如下。

const p1 = new Promise((resolve, reject) => {
    //成功
  resolve(1);
});
//成功
const p2 = Promise.resolve(1);
//失败
const p3 = Promise.reject(2);
const pAll = Promise.all([p1, p2, p3]);
pAll.then(
  values => {
    console.log(values);
  },
  reason => {
    console.log(reason);
  }
);

代码解析如下。

因为 p3 是错误的数据,所以运行上述代码,最终会执行 reason 回调函数,执行结果就是 p3 中定义的错误数据 “2”,如图 9-3 所示。

image 2025 02 11 17 26 26 282
Figure 2. 图9-3 执行reason回调函数

如果把 p3 删掉,此时 p1 和 p2 都是定义成功的 promise 对象,所以 pAll.then() 会执行成功的回调函数。

执行结果会以数组的形式打印,运行结果如图 9-4 所示。

image 2025 02 11 17 27 21 953
Figure 3. 图9-4 pAll执行结果

Promise.race()方法:(promises) ⇒ {}

promises:多个promise对象的数组。

作用:返回一个新的promise对象。第一个完成promise的状态就是最终的结果状态。

async function fn1() {
  return "hello async";
}

const res = fn1();
console.log(res);

代码解析如下。

当前 p1、p2、p3 都没有延迟,所以第一个执行完成的就是 p1,pRace.then() 最终调用的状态就是 p1 的状态。在没有延迟的情况下,最终的状态就是数组中第一个 promise 对象的状态。

async与await

当前,async 与 await 是编写异步操作的解决方案,也是建立在 promise 基础上的新写法,两者同时使用,如果在方法中使用了 await,那么方法前面必须加上 async。

async函数

作用:返回promise对象。

async 的右侧是一个函数,函数的返回值是 promise 对象。

const p1 = new Promise((resolve, reject) => {
    //成功
    resolve(1);
});
//成功
const p2 = Promise.resolve(2);
//失败
const p3 = Promise.reject(3);
const pRace = Promise.race([p1, p2, p3]);
pRace.then(
  value => {
    console.log(value);
  },
  reason => {
    console.log(reason);
  }
);

代码解析如下。

如果 fn1() 函数前面不加 async,毫无疑问,res 的打印结果应该是 hello async,加上 async 之后将打印 promise 对象,如图 9-5 所示。

image 2025 02 11 17 33 29 365
Figure 4. 图9-5 res打印结果

既然返回的是 promise 对象,获取数据就要使用 .then 方法,并且要设置成功的回调函数和失败的回调函数,代码如下。

async function fn1() {
  return "hello async";
}

const res = fn1();

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

运行代码,此时在 .then 中执行的是成功的回调函数,如图 9-6 所示。

image 2025 02 11 17 35 02 024
Figure 5. 图9-6 .then执行成功的回调函数

加上 async 之后,返回的是 promise 对象,我们也可以直接在 fn1 函数中设置返回失败的数据,代码如下。

async function fn1() {
  return Promise.reject('失败的数据');
}

const res = fn1();

res.then(
  value => {
    console.log(value);
  },
  reason => {
    console.log(reason); // 输出:失败的数据
  }
);

运行结果如图 9-7 所示。

image 2025 02 11 17 36 52 021
Figure 6. 图9-7 res.then执行结果

通过上述案例,可以得出两个结论。

(1)async函数的返回值为promise对象。 (2)promise对象的结果,由async函数执行的返回值决定。

await表达式

作用:等待 async 函数执行完成,并返回 async 函数成功的值,代码如下。

async function fn1() {
  return 'Hello async';
}

async function fn2() {
  const res = await fn1();
  console.log(res);
}

fn2();

代码解析如下。

fn1() 函数的返回值是 promise 对象。在 fn2() 函数中,await 获取到的是 hello async,表示使用 await 可以直接获取到 promise 对象成功的值,不需要使用 .then() 方法,运行结果如图 9-8 所示。

image 2025 02 11 17 38 58 581
Figure 7. 图9-8 await获取到的是hello async

await 必须写在 async 函数中,但 async 函数中可以没有 await。

扩展

fn1() 函数的返回值是 promise,并且设置的是成功的数据。那么如果 fn1() 函数返回的是失败的数据,上述代码可以正常运行吗?代码如下。

async function fn1() {
  return Promise.reject('失败数据');
}

async function fn2() {
  try {
    const res = await fn1();
    console.log(res);
  } catch (error) {
    console.error(error); // 输出:失败数据
  }
}

fn2();

运行结果如图 9-9 所示。

image 2025 02 11 17 44 27 121
Figure 8. 图9-9 await等待结果

结论:await 只能等待 promise 对象返回成功的数据,如果 promise 返回的是失败的数据,直接使用 await 则会报错,应该使用 try/catch 获取失败结果,最终代码如下。

async function fn1() {
  return Promise.reject('失败数据');
}

async function fn2() {
  try {
    const res = await fn1();
    console.log(res);
  } catch (error) {
    console.log(error); // 输出:失败数据
  }
}

fn2();