要手写 Promise 我们必须先要了解 Promise 的作用以及设计思想。
Promise 对象是 Javascript 的异步操作解决方式,为异步操作提供统一接口。它的设计思想是,所有异步任务都返回要给 Promise 实例。
在 Promise 出来之前,我们通常是使用回调函数来处理异步操作。
1 | function cb(err, data) { |
很熟悉对吧,接下来我们看一下 Promise 怎么写(其实我也不是很懂这里)
1 | function emmm(resolve) { |
状态
Promise 对象通过自身的状态来控制异步操作。Promise 实例具有三种状态:
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
但是这三种状态只有两种变化途径
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态变化,就会有新的状态变化。因此,Promise 的最终结果只有两种
- 异步操作成功,Promise 实例返回一个值(value),状态变为 fulfilled
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为 rejected
构造函数
Javascript 提供原生的 Promise 构造函数,用来生成 Promise 实例。
1 | const promise = new Promise(function (resolve,reject) { |
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject,它们是两个函数,由 javascript 引擎提供,不用自己实现。
resolve 函数的作用是,将 Promise 实例的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。并交由传入 then 的回调函数执行。
reject 函数的作用是,将 Promise 实例的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时,并将异步操作报出的错误作为参数传递出去。并交由传入 then 的回调函数执行。
1 | function timeout(ms) { |
Promise.prototype.then()
Promise 实例的 then 方法是用来添加回调函数的。
then 方法可以接受两个回调函数,第一个是异步操作成功时的回调函数,第二个时异步操作失败时的回调函数(可以省略)。
1 | const p1 = new Promise((resolve, reject) => { |
then 的链式调用中,如果要让下一个 then 获取到上一个 then 的值,那么上一个 then 就必须要返回值。
最后一个 then 方法,回调函数是 console.log 和 console.error,用法上有一个重要的区别。console.lo 只显示 step3 的返回值,而 console.error 可以显示 p1、step1、step2、step3 之中任意一个发生的错误。Promise 对象的报错具有传递性。
Promise 的用法简单说就是一句话:使用 then 方法添加回调函数。但是不同的写法有细微的差别。
1 | f1().then(function() { |
例子
图片加载
1 | const preloadImage = function(path) { |
异步请求
不想写
微任务
微任务这一方面的内容关系到 js 的运行机制,这是另一个篇幅的内容了。但是在这里简单的了解一下。
Promise 的回调函数属于异步任务,会在同步任务之后运行。
1 | new Promise(function(resolve, reject) { |
上面代码先输出 1,然后是 3, 最后是 2,我们在构造 Promise 实例时传入的函数是会立即执行的,相当于同步任务,之后的 console.log(3) 也属于同步任务,而 then 的回调函数属于异步任务,一定晚于同步任务执行。
但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于:正常任务追加到下一轮时间循环,微任务追加到本轮时间循环。这意味着,微任务的执行时间一定早于正常任务。
1 | setTimeout(() => { |
async & await
这里简单介绍一下 async 和 await。他们就是语法糖功能,可以使用生成器实现。
我们可以参考一下 MDN 官网对
async 和
await 的解释以及作用。
简单来说 saync 允许声明一个函数为一个包含异步操作的函数。
await 操作符用于等待一个Promise 对象。它只能
在异步函数 async function 中使用。
await 后面一般接 promise 对象或者表达式,且 await 返回的是这个 promise 对象的执行结果。
那我们的基本用法大概就是这样了
1 | function p1() { |