「时光不负,创作不停,本文正在参加2022年中总结征文大赛」
前言
Promise是ES6的非常重要一个知识点,本文从 Promise的概念、Promise解决的痛点问题即为什么会出现Promise、Promise的基本使用方法、手写Promis来介绍Promise。
1、举一个生活中的栗子,引出话题
Promise,翻译过来就是承诺、保证、发誓的意思。红宝书则翻译成期约。我们一般直接就称呼它为Promise,dddd。
我中午去新丰小吃点了一碗大排面,付款之后前台会给我一张取餐小票。这张小票其实就是一个承诺,小吃店承诺给我一碗大排面。我拿着小票走到小店的最里面的窗口排队,通过小票可以换取大排面。 需要注意的是:
这个承诺是有三种不同的状态
1、等待状态 在窗口前排队等待,面条还没做好,我可以做其他的事情,比如跟同事聊天
2、成功状态 交出小票,取餐成功
3、失败状态  窗口师傅说没有大排面了,555555
状态一旦改变就不能逆转,比如你成功拿到大排面之后就不能再去排队进入等待状态了。 这里的取餐小票就是一个Promise,它是小店承诺给你餐品的凭证,不管结果是成功失败,他都会给你一个结果。在得到结果之前,你可以做别的事。
2、Promise的概念
Promise是一个类,它可以更优秀地处理异步任务,承诺给调用者回调数据。
当我们创建一个Promise对象,需要给对象传入一个回调函数。
这个回调函数会立即执行,并传入另外两个回调函数resolve和reject
当我们调用 resolve函数时,会执行对象的then方法的内容
当我们调用 reject函数时,会执行对象的 catch方法的内容
2.1 Promises/A+规范
Promises/A+规范是 ECMAScript 6 规范实现的范本。ECMAScript 6 增加了对 Promises/A+规范的完善支持,即 Promise 类型。一经推出,Promise 就大受欢迎,成为了主导性的异步编程机制。 [Promise/A+规范](Promises/A+ (promisesaplus.com))
3、为什么会出现Promise
每个技术的出现都是为了解决先前旧技术的痛点问题,对旧技术而言有一定的革命性。那到底Promise解决了什么样的问题呢?
在以往的异步编程中,通常用回调函数来实现异步操作。
当回调函数出现返回值依赖时,往往有多个回调,一不小心就有回调地狱的nightmare。下面用定时器来模拟异步操作。
// request.js
function requestData(url, successCallBack, failCallBack) {
setTimeout(() => {
if(url === 'htttp://baidu.com') {
const names = ['1', '2', '3'] // 这边把 数据传不出去
successCallBack(names) // 只能通过回调函数来把数据传递出去
}
else {
const errorMsg = '错误的返回'
failCallBack(errorMsg)
}
}, 3000);
}
// main.js
requestData('htttp://baidu.com', (res)=> {console.log(res)}, (err)=> { console.log(err)})
4、Promise的基本使用
const promise = new Promise((resolve, reject) => {
// do other things
if(`成功的回调`) {
resolve()
} else {
reject() // 失败的回调
}
})
promise.then(res =>{
console.log(res, '成功的结果')
}).catch(err => {
console.log(err, '失败的结果')
})
3.1 三种状态
pending(待定) 最初始的状态,该状态可以变成下面任意一个状态
fullfiled/resolved(兑现/解决) 即回调成功的状态
rejected (拒绝) 即回调失败的状态
需要注意的是,状态的改变是不可逆的。从初始的pending(待定状态)可以转变为 fullfilled/resolved(解决状态);或者从初始的pending(待定状态) 可以转变成 rejected(拒绝状态)。处于pending状态可以做其他的事情。
可以用常量表示这三种状态
const PROMISE_STATUS_PENDING = "pengding"
const PROMISE_STATUS_FULLFILLED = "fullfilled"
const PROMISE_STATUS_REJECTED = "rejected"
当创建Promise对象时,我们需要传入一个回调函数,我们称之为executor;
这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;所以大致框架是:
class LAPromise {
constructor(executor) {
// ....
executor(resolve, reject)
}
}
完整的
resolve方法 改变状态为 fullfilled
rejected方法 改变状态为 rejected
const PROMISE_STATUS_PENDING = "pengding"
const PROMISE_STATUS_FULLFILLED = "fullfilled"
const PROMISE_STATUS_REJECTED = "rejected"
class LAPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if(this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULLFILLED
console.log('执行resolve方法')
}
}
const reject = (reason) => {
if(this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
console.log('执行了reject方法')
}
}
executor(resolve, reject)
}
}
3.2 对象方法- then()方法
then方法是定义在 Promise.prototype.then里面
then方法接受两个参数:
fullfilled的回调函数:当状态变成fulfilled时会回调的函数
rejeted的回调函数:当状态变成 rejected时会回调的函数
promise.then(onFulfilled, onRejected)
class LAPromise {
then(onFulfilled, onRejected) {
if(this.status === PROMISE_STATUS_FULLFILLED) {
onFulfilled(this.value)
} else if(this.status === PROMISE_STATUS_REJECTED ) {
onRejected(this.reason)
}
}
}
运行试试看
const p = new LAPromise((resolve, reject) => {
resolve('abc')
rejected('xyz')
})
p.then(res => {
console.log('res', '请求成功')
}, err => {
console.log('err', '请求失败')
})
打印: 执行resolve方法
res 请求成功
继续优化 then方法
3.2.1、当参数onFulfilled或者onRejected 不是函数时,则忽略这个参数
class LAPromise {
then(onFulfilled, onRejected) {
onFulfilled = Object.prototype.toString.call(onFulfilled) === '[object Function]' ? onFulfilled : value => value
onRejected = Object.prototype.toString.call(onFulfilled) === '[object Function]' ? onRejected : error => {throw error}
}
}
3.2.2、then 方法可以多次调用
每次调用我们都可以传入对应的fulfilled回调;
当Promise的状态变成fulfilled的时候,这些回调函数都会被执行
promise.then(res => {
console.log('res1', res)
})
promise.then(res => {
console.log('res2', res)
})
promise.then(res => {
console.log('res3', res)
})
用数组将onFulfilled, onRejected 收集起来,再依次执行,达到可以多次调用的目的。
class LAPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectedFns = []
const resolve = (value) => {
if(this.status === PROMISE_STATUS_PENDING ) {
this.value = value
this.status = PROMISE_STATUS_FULLFILLED
this.onFulfilledFns.forEach(fn => fn(this.value))
console.log('resolve方法执行')
}
}
const reject = (reason) => {
if(this.status === PROMISE_STATUS_PENDING) {
this.reason = reason
this.status = PROMISE_STATUS_REJECTED
this.onRejectedFns.forEach(fn => fn(this.reason))
console.log('rejected方法执行')
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
onFulfilled = Object.prototype.toString.call(onFulfilled) === '[object Function]' ? onFulfilled : value => value
onRejected = Object.prototype.toString.call(onFulfilled) === '[object Function]' ? onRejected : error => {throw error}
if(this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(onFulfilled)
this.onRejectedFns.push(onRejected)
} else if(this.status === PROMISE_STATUS_FULLFILLED ) {
onFulfilled(this.value)
} else {
onRejected(this.reason)
}
}
}
运行试试看
const p = new LAPromise((resolve, reject)=> {
resolve('123')
reject('456')
})
p.then(res => {
console.log('res1', res)
})
p.then(res => {
console.log('res2', res)
})
打印结果
res1 123
res2 123
3.3.3 then的链式调用
then可以进行链式调用是因为在then方法返回了新的 Promise作为回调返回值
// 链式调用的使用
const p = new Promise((resolve, reject) => {
resolve('111')
})
p.then((res) => {
console.log('res1', res)
return 'a' // 可以返回一个返回值
}).then((res) => {
console.log('res2', res) // 接受上面返回的值
})
实现: 改写 then方法
then(onFulfilled, onRejected){
return new HYPromise((resolve, reject) => {
if(this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(err) {
reject(err)
}
})
this.onRejectedFns.push(() => {
try {
const value = onRejected(this.reason)
resolve(value)
} catch(err) {
reject(err)
}
})
} else if(this.status === PROMISE_STATUS_FULLFILLED) {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(err) {
reject(err)
}
} else {
try{
const value = onRejected(this.reason)
resolve(value)
} catch(err){
reject(err)
}
}
})
}
优化点: 太多的try...catch...,而且格式类似,可以抽成函数
运行试试看
const promise = new LAPromise((resolve, reject) => {
resolve('aaa')
})
promise.then(res => {
console.log('res1', res)
return '1'
}).then(res => {
console.log('res2', res)
})
res1 aaa
res2 1
3.3.4 事件循环
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,
还依靠任务队列(task queue)来决定另外一些代码的执行。整个执行过程,我们称为事件循环过程。
事件循环中,不仅只有一个队列,有两个队列,一个微任务一个宏任务
- 宏任务队列:
- 微任务队列:
宏任务队列 | 微任务队列 |
---|---|
定时器 | queuequeueMicrotask |
ajax | Promise.then |
dom(比如点击事件) | mutation observer api |
- 事件循环中,两队列的优先级
- main script 先执行
- 在执行任何一个宏任务之前,都会查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的
- 如果不为空就执行微任务队列的任务
下面举个例子说明一下
new Promise(function(resolve) {
console.log("promise1")
resolve()
}).then(function() {
console.log('then1')
})
setTimeout(function(){
console.log("setTimeout1")
});
console.log(2)
queueMicrotask( ()=> {
console.log("queueMicrotask1")
})
答案: promise1 2 then1 queueMicrotask1 setTimeout1
main script | 微任务 | 宏任务 |
---|---|---|
promise1 | then1 | setTimeout1 |
2 | queueMicrotask1 |
用queueMicrotask实现微任务
class HYPromise {
constructor(exector) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectedFns = []
const resolve = (value) => {
queueMicrotask(() => {
if(this.status === PROMISE_STATUS_PENDING && this.onFulfilledFns){
this.status = PROMISE_STATUS_FULLFILLED
this.value = value
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
}
})
}
const reject = (reason) => {
queueMicrotask(() => {
if(this.status === PROMISE_STATUS_PENDING && this.onRejectedFns) {
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
this.onRejectedFns.forEach(fn =>{
fn(this.reason)
})
}
})
}
try{
exector(resolve, reject)
} catch(err){
reject(err)
}
// exector(resolve, reject)
}
then(onFulfilled, onRejected){
return new HYPromise((resolve, reject) => {
if(this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(err) {
reject(err)
}
})
this.onRejectedFns.push(() => {
try {
const value = onRejected(this.reason)
resolve(value)
} catch(err) {
reject(err)
}
})
} else if(this.status === PROMISE_STATUS_FULLFILLED) {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(err) {
reject(err)
}
} else {
try{
const value = onRejected(this.reason)
resolve(value)
} catch(err){
reject(err)
}
}
})
}
}
总结
以上就是我们基于Promises/A+规范实现的Promise,我们还可以进一步优化,对照着规范练习。
Promise是对以往异步编程造成回调地狱的提供的解决办法,他可以为异步任务添加回调来得到异步任务的结果。异步任务的结果出现在Promise.then里面。结构清晰容易理解。
今日父亲节
我一直很庆幸自己有一个很理想的父亲。张爱玲说过,“中年的男人,时常会觉得孤独,因为他一睁开眼睛,周围都是要依靠他的人,却没有他可以依靠的人。” 在父亲的节日里,跟父亲说一点贴心sweet的话吧。
我的父亲在年轻的时候还是很帅的,眼睛深邃有神大双眼皮,还有酒窝。
我的父亲没什么学历,小时候只给我讲过一个故事,鲁班发明锯子的故事。但是他勤劳能干肯吃苦,辛苦赚钱养家。
我的父亲虽然喜欢叨叨叨的,但是他爱妻子,爱孩子,孝顺爷爷和姥姥姥爷。首先他很尊重我妈妈的想法,重视妈妈的声音,其实我妈妈就是家里的领导,我们时常感觉到领导的pua。我爸爸特别喜欢做家务,喜欢刷碗刷锅喜欢做菜扫地拖地,这其实让我这个懒人很匪夷所思。
感谢爸爸一直以来的付出,给我们一个完整有爱的家。