深入理解 Promise,看看你会几道题

1. Promise A+规范的基本概念 Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一 这套规范最早诞生于前端社区,规范名称为 Promise A+ 该规范出现后,立即得到了很多开...

这篇文章已从掘金同步到个人博客,原始发布地址为 掘金原文

1. Promise A+规范的基本概念

Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一

这套规范最早诞生于前端社区,规范名称为Promise A+

该规范出现后,立即得到了很多开发者的响应

Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象,该对象称之为Promise对象,也叫做任务对象

  2. 每个任务对象,都应该有两个阶段、三个状态

    根据常理,它们之间存在以下逻辑:

    • 任务总是从未决阶段变到已决阶段,无法逆行
    • 任务总是从挂起状态变到完成或失败状态,无法逆行
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变 image.png
  3. 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

  4. 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected

2. 创建Promise

// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
  // 任务的具体执行流程,该函数会立即被执行
  // 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
  // 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});

pro.then(
  (data) => {
    // onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
  },
  (reason) => {
    // onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
  }
);

3. 针对Promise进行后续处理

下面任务的最终状态是什么,相关数据或失败原因是什么,最终输出是什么?

const pro1 = new Promise((resolve, reject) => {
    console.log('开始任务');
    resolve(1);
    reject(2);
    resolve(3);
    console.log('结束任务');
})
console.log(pro1);

const pro2 = new Promise((resolve, reject) => {
    console.log('开始任务');
    resolve(1);
    resolve(2);
    console.log('结束任务');
})
console.log(pro2);

demo1_log.png

4.Promise链式调用

  1. then方法必须会返回一个新的promise, 可以理解为 后续处理也是一个任务

  2. 新任务的状态取决于后续处理:

    • 若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的状态。
    • 若有后续处理但未执行,新任务挂起。
    • 若后续处理执行了,则根据后续处理的情况确定新任务的状态。
      • 后续处理执行无错,新任务的状态未完成,数据为后续处理的返回值。
      • 后续处理执行有错,新任务的状态为失败,数据为异常数据。
      • 后续执行后返回时一个任务对象,新任务的状态和数据与该任务对象一致。

为了更直观的练习链式调用,来看几个题目:

// 下面代码的输出结果是什么
const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.then((data) => {
  console.log(data);
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);

image.png

再来一道,稍微改一下,把pro2的then改成catch:

const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.catch((data) => {
  console.log(data);
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);

image.png

再来改一下,给pro2抛一个错

const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.then((data) => {
  throw 3;
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);

image.png

继续下一道题目:

const pro = new Promise((resolve, reject) => {
    resolve(1)
}).then((res) => {
    console.log(res);
    return 2;
}).catch((err) => {
    return 3;
}).then((res) => {
    console.log(res);
})

cnosole.log(pro)

image.png

再稍微改造一下:

const pro = new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res.toString());
    return 2;
}).catch((err) => {
    return 3;
}).then((res) => {
    console.log(res);
})
console.log(pro)

image.png

继续下一道:

new Promise((resolve, reject) => {
  resolve(1)
})
  .then((res) => {
    console.log(res);
    return new Error('2');
  })
  .catch((err) => {
    throw err;
    return 3;
  })
  .then((res) => {
    console.log(res);
  });

image.png

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject();
  }, 1000);
});
const promise2 = promise1.catch(() => {
  return 2;
});

console.log('promise1', promise1);
console.log('promise2', promise2);

setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000);

image.png

换一个类型的题目:

const promise = new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
        resolve();
        console.log(3);
    });
}).then(() => {
    console.log(4);
})

console.log(5);

image.png

setTimeout(() => {
    console.log(1);
});

const promise = new Promise((resolve, reject) => {
    console.log(2);
    resolve();
}).then(() => {
    console.log(3);
})
console.log(4);

image.png

来个简单一点的:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject();
  }, 1000);
});
const promise2 = promise1.catch(() => {
  return 2;
});

console.log('promise1', promise1);
console.log('promise2', promise2);

setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000);

image.png

5. async await 消除回调

async function m(){
    console.log(0);
    const n = await 1;
    console.log(n);
}
m();
console.log(2);

image.png

再来一道:

async function m() {
  console.log(0);
  const n = await 1;
  console.log(n);
}

(async () => {
  await m();
  console.log(2);
})();

console.log(3);

image.png

继续下一道:

async function m1() {
  return 1;
}

async function m2() {
  const n = await m1();
  console.log(n);
  return 2;
}

async function m3() {
  const n = m2();
  console.log(n);
  return 3;
}

m3().then((n) => {
  console.log(n);
});

m3();

console.log(4);

image.png

累了没?那来一道看起来比较简单的开火车题目:

Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);

image.png

来一个比较经典的问题,你可能会比较熟悉:

var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(() => {
    resolve();
  }, 1000);
})
  .then(() => {
    console.log('promise2');
  })
  .then(() => {
    console.log('promise3');
  })
  .then(() => {
    console.log('promise4');
  });

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
});

console.log('end');

image.png

继续下一道,我怕再玩下去,你要忍不住到要打我了:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');

image.png

最后一道,绝对颠覆你对finally的认知:

Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })

打印结果:

image.png

为了方便理解,先看看代码:

Promise.resolve('1')
  .then(res => {
    console.log(res)  // 打印 '1'
  })
  .finally(() => {
    console.log('finally')  // 打印 'finally'
  })

Promise.resolve('2')
  .finally(() => {
    console.log('finally2')  // 打印 'finally2'
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)  // 打印 'finally2后面的then函数 2'
  })

运行结果是:

1
finally2
finally
finally2后面的then函数 2

你好奇的是:为什么在打印 '1' 后,没有紧接着打印 'finally',而是先打印了 'finally2'

这与 JavaScript 的 微任务调度机制 有关。让我们一步步拆解:

1. Promise 的链式调用与微任务队列

2. 同步代码的执行

当代码运行时,同步部分会立即执行:

同步代码执行完后,微任务队列的初始状态是:

3. 微任务的逐步执行

JavaScript 的事件循环会在同步代码执行完后,清空微任务队列。让我们看看具体过程:

4. 关键点解答

为什么在打印 '1' 后没有立即执行 'finally'?原因在于:

为了更直观地展示,以下是队列的演变过程:

  1. 初始状态(同步代码后):

    • 队列:[第一个 Promise 的 .then, 第二个 Promise 的 .finally]
  2. 执行 .then 后(打印 '1'):

    • 队列:[第二个 Promise 的 .finally, 第一个 Promise 的 .finally]
  3. 执行第二个 .finally 后(打印 'finally2'):

    • 队列:[第一个 Promise 的 .finally, 第二个 Promise 的 .then]
  4. 执行第一个 .finally 后(打印 'finally'):

    • 队列:[第二个 Promise 的 .then]
  5. 执行第二个 .then 后(打印 'finally2后面的then函数 2'):

    • 队列:空

在执行第一个 Promise 的 .then 并打印 '1' 后,它的 .finally 没有立即执行,是因为 .finally 的回调是在 .then 执行后才加入微任务队列的。而第二个 Promise 的 .finally 在同步代码阶段就已加入队列,排在前面。因此,微任务调度机制决定先执行 finally2,然后才执行第一个 Promise 的 finally。这种顺序是由微任务队列的 FIFO 特性决定的,确保了异步任务的执行有条不紊。

总结