看过大概十几集关于promise的视频,也搜索过掘金里面的很多文章。但是总觉得对于promise一知半解,而且无法形成知识体系,网上资料知识点过于杂乱。所以这里分享一下我对promise的理解。
一、PromiseA+规范
首先是PromiseA+规范,即是一个规则,也是一种标准。
记得刚开始看Promise,看着网上各种各样的文章一头雾水,于是沉下心来想看PromiseA+规范。 想看看一个标准的Promise应该有怎么样的要求。于是通读了翻译成中文的PromiseA+规范。
这里给出原文以及我看的译文地址,以便大家学习:
PromiseA+规范:promisesaplus.com/
PromiseA+规范译文:malcolmyu.github.io/malnote/201…
github:github.com/promises-ap…
规范中有这么一句话:
一个开放、健全且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。
我觉得这句话是非常重要的,我们不需要一个严苛一成不变的标准,在学习Promise的时候我们 更应该思考的是为什么开发者设计出来出来这种标准,怎么样有利于我们的开发。又是怎么实现它的便捷性的。
1、首先什么是promise
promise表示一个异步操作的结果,与之相配的是then方法,该方法注册了两个回调函数分别是resolve和reject。又或者说 promise 是一个拥有 then 方法的对象或函数。
2、解决(fullfill/ resolve)
指一个promise成功时进行的的一系列操作。
3、拒绝(reject)
指一个promise失败时进行的一系列操作。
4、终值(value)
指promise被解决时传递给解决回调的值
5、原因(reason):
指promise被拒绝时传递给拒绝回调的值
6、thenable
是一个定义了then方法的对象或者函数
7、值value
指任何JavaScript的合法值,包括 undefined , thenable 和 promise
8、异常exception
是使用 throw 语句抛出的一个值
9、拒因reason
表示一个 promise 的拒绝原因。
10、promise的状态
(1)等待态pending,翻译过来就是【未决定的】,也就是说当前promise的状态还没有被改变,promise还没有成功或者失败,此时可以迁移至执行态或拒绝态。
(2)执行态fullfilled/resolved,此时promise已经成功而且拥有不可变的终值value
(3)拒绝态rejected,此时promise已经失败而且拥有不可变的原因reason
11、then方法
(1)then方法就是提供来访问promise状态的
这个then方法接受两个参数,分别是onFulfuilled和onRejected
promise.then(onFulfilled, onRejected)
(2)then方法可以被多次调用比如
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 1000);
}).then(
value => {},
reason => {}
).then(
value => {},
reason => {}
).catch(
reason => {}
)
(3) then的返回
then方法必须返回一个promise对象
注意:比如我们在下面这段代码中,不论 promise1 被 reject 还是被 resolve 时 它的返回值 都会被 resolve,只有出现异常时才会被 rejected。
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 1000);
}).then(
value => {
console.log("value1 " + value);
},
reason => {
console.log("reason1 " + reason);
}
).then(
value => {
console.log("value2 " + value);
},
reason => {
console.log("reason2 " + reason);
}
).catch(
reason => {
console.log("catch");
}
)
二、为什么要用promsie?
1、promise是Js异步编程的一种解决方案。已经被ES6纳入规范中。
2、指定回调函数的方式更加灵活
我们知道,旧的回调函数必须在启动异步任务前指定,
而现在的promise的过程是这样的:
启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步之后)
3、支持链式调用,可以解决回调地狱问题
(1)什么是回调地狱?回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。说白了就是不停的回调嵌套,导致整个代码非常的不优雅。
(2)回调地狱的缺点? 不便于阅读 / 不便于异常处理,代码臃肿、可读性差、耦合度过高、代码复用性差
(3)解决方案? promise链式调用(终极解决方案? async / await)
三、promise的基本使用
我们从上面可以看到,promise是Js异步编程的一种解决方案,支持链式调用,解决回调地狱。 接下来就看看他的基本使用。
// 1、创建一个新的promise对象
const p = new Promise((resolve, reject) => { // 执行器函数,执行异步操作 同步回调
// 2、执行异步操作任务
setTimeout(() => {
const time = Date.now()
if (time % 2 == 0) {
// 3.1 如果成功,调用resolve(value)
resolve('成功的数据' + time)
} else {
// 3.2 如果失败,调用reject(reason)
reject('失败的数据' + time)
}
}, 1000);
})
p.then(
value => { // 接收得到成功的value数据 onResolved
console.log('成功的回调', value)
},
reason => { // 接受得到失败的reason数据 onRejected
console.log('失败的回调', reason)
}
)
除此之外,我们平时也可以这样使用:
function usePromise(id) {
return new Promise((resolve, reject) => {
if (id == 1) {
console.log(1)
resolve(1)
} else {
console.log(false)
reject(2)
}
})
}
usePromise(1).then(
id => {
console.log(id);
}
)
另外,这里列出promise常用的API,也是后面的手写Promise的依据
Promise.resolve(value) --类方法,返回的状态为resolved
Promise.reject(reason) --类方法,返回的状态为rejected
Promise.prototype.then --实例方法,注册回调函数
Promise.prototype.catch --实例方法,捕获异常
Promise.race -- 类方法,返回最先执行的promise任务结果
Promise.all -- 类方法,返回所有任务成功的结果或者其中一个任务失败的结果
四、关于宏任务和微任务
此处要提到宏队列和微队列,因为我们在理解Promise的then执行顺序的时候需要了解到这两个概念。 我这里就简单的解释一下,这两个概念还涉及到JavaScript V8引擎,这里也不多做详细解释。
1、宏任务:
为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通的消息队列。然后主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务。
2、微任务:
微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。
我们常见的宏队列任务有:dom事件回调、ajax回调、定时器回调 常见的微队列任务有:promise回调、mutation回调
setTimeout(() => { // 会立即放到宏队列
console.log('timeout callback');
}, 0);
setTimeout(() => { // 会立即放到宏队列
console.log('timeout callback');
}, 0);
Promise.resolve(1).then(
value => { // 会立即放入微队列
console.log('Promise onResolved', value)
}
)
Promise.resolve(2).then(
value => { // 会立即放入微队列
console.log('Promise onResolved', value)
}
)
五、promise练习题
接下来给出几道比较简单的关于promise执行顺序的题,结合宏队列和微队列以及异步操作,可以很容易的做出来。我就不给出具体的解析了,如果有不懂的可以在评论问或者查资料~~
1、写出这段代码执行的结果:
setTimeout(() => { // 宏队列异步
console.log(1);
}, 0);
Promise.resolve().then(() => { // 微队列异步
console.log(2);
})
Promise.resolve().then(() => { // 微队列异步
console.log(3);
})
console.log(4); // 同步
执行结果:4 2 3 1
2、写出这段代码执行的结果:
setTimeout(() => { // 宏队列异步
console.log(1);
}, 0);
new Promise((resolve) => {
console.log(2);
resolve()
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
})
console.log(5);
执行结果:2 5 3 4 1
3、写出这段代码执行的结果:
const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6) // 因为只改变一次,所以这个resolve无效
}, 0);
resolve(1)
})
resolve(2)
p.then((arg) => {
console.log(arg); //1
})
}))
first().then((arg) => {
console.log(arg); //2
})
console.log(4);
执行结果:3 7 4 1 2 5
4、写出这段代码执行的结果:
setTimeout(() => {
console.log(0);
}, 0);
new Promise((resolve, reject) => {
console.log(1);
resolve()
}).then(() => {
console.log(2);
new Promise((resolve, reject) => {
console.log(3);
resolve()
}).then(() => {
console.log(4);
}).then(() => {
console.log(5);
})
}).then(() => {
console.log(6);
})
new Promise((resolve, reject) => {
console.log(7);
resolve()
}).then(() => {
console.log(8);
})
执行结果:1 7 2 3 8 4 6 5 0
另外,关于比较重要的手写promise,我还在想怎么写比较方便记忆并且便捷使用,希望能尽快搞懂它~~