专业的编程技术博客社区

网站首页 > 博客文章 正文

Js关于Promise的那些问题(js关于promise的那些问题分析)

baijin 2024-10-11 10:46:46 博客文章 14 ℃ 0 评论

关于Promise,每次总感觉似懂非懂,知道它是用于处理异步操作的,但是说起来还是不成体系。所以花一些时间,把它真正弄明白,包括一些Promise的代码结果问题。

Promise

了解什么是异步

可以在执行一个可能长期运行的任务的同时继续对其他事件做出反应而不必等待任务完成。

什么是Promise

Promise是一个对象,代表一个异步操作的最终完成或失败以及其结果。它的出现主要是为了解决异步编程中出现的一些问题,例如回调地狱、错误处理困难等问题。
举个简单的回调地狱的:

// 需要通过高德天气接口获取当前位置的天气,但是需要先知道当前位置的地址编码。
// 那么就需要先调取接口获取当前位置信息。所以天气接口的调用需要依赖地址接口请求结果。
// promise出现之前
func getAddrCode(callback) {
    const addrcode = api1() // 请求第一个接口 获取地址编码
    callback(null, addrcode); // 假设没有错误,传递数据给下一个回调  
}
func getWeather(addrCode, callback) {
    const weather = api2(addrCode) // 请求第二个接口,传入地址编码,获取天气 
    callback(null, weather); // 假设没有错误,传递最终数据  
}
// 使用嵌套的回调函数来获取数据  
getAddrCode((err, addrcode) => {  
    if (err) {  
        console.error('Error fetching data from API 1:', err);  
        return;  
    }  
    getWeather(addrcode, (err, weather) => {  
        if (err) {  
            console.error('Error fetching data from API 2:', err);  
            return;  
        }  
        console.log('Final data:', weather);   
    });  
});
// 这只是两个接口,如果是四个五个甚至更多接口呢?
// 代码的可读性也会变得很差。

所以,Promise出现了。先简单了解下Promise再来看它是怎么优化上面代码的。

Promise有三个状态 pending(进行中)、fulfilled(已成功)、rejected(已失败),状态不受外界影响,一旦改变就不会再变

通过Promise的构造函数可以知道:Promise()接收两个函数作为参数,resovle和reject。

  • resolve(): 将状态从pending变成fulfilled,在执行成功时调用,将结果作为参数传递出去; 如果传入的参数是绑定的Promise对象,则会抛出错误TypeError; 如果传入的参数是另一个promise实例,那么传入实例状态决定当前实例状态。
  • reject():将状态从pending变成rejected,在执行失败时调用,将报错作为参数传递出去。
const promise = new Promise((resolve, reject) {
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
})

实例方法

then

使用构造函数生成Promise实例后可以使用then方法接收resolved状态和rejected状态的回调函数。then方法返回一个新的Promise对象,该对象状态取决于传递给 then() 方法的回调函数的行为以及原始 Promise 的状态。

// 语法
then(onFulfilled)
then(onFulfilled, onRejected)

// 举个例子
const promise = new Promise((resolve, reject) {
    if (/* 异步操作成功 */){
        resolve("Success");
    } else {
        reject(error);
    }
})
// 声明一个变量接收成功返回值
let res1
promise.then(res => {
    res1 = res
})
setTimeout(() => {
  console.log(res1, "res1")
})

// ?错误写法:
const res1 = promise.then(res => {
    return res
})
// 打印res1会得到一个Promise对象。

catch

对于then(undefinde, onRejected),有一种简写形式:Promise.catch(),用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的Promise对象。

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。如果没有使用catch()方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

语法:catch(onRejected)
// 
const promise = new Promise((resolve, reject) => {
  reject(new Error('test'));
});
promise.catch((error) => {
  console.log(error);
});

finally

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。其不接受任何参数,与Promise的状态、执行结果无关。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

那么学习完基础部分,对于Promise的使用,有了一个大概的了解。接下来看看怎么对上面的回调地狱例子做优化:

// 请求返回一个Promise对象
function getAddrCode() {
    return new Promise((resolve, reject) => {
        const addrCode = api1()
        resovle(addrCode)
    })
}
function getWeather(addrCode) {
    return new Promise((resolve, reject) => {
        const res = api2(addrCode)
        resovle(addrCode)
    })
}

getAddr().then(res => {
    return getWeather(res)
})// 假设有api3 api4, 那么就接着.then下去
.catch(err => {
    console.error(err)
})

Promise的静态方法

除了以上的then、catch、finally这三个实例方法,Promise还有几个静态方法。

Promise.all()

参数接受一个可迭代对象,一般来说是数组,数组元素都是Promise实例,当数组元素出现非Promise实例时,会将其视为一个已经解决的 Promise,解析值为元素本身。

返回值是一个新的Promise对象,状态由成员决定:
如果所有成员状态都是fulfilled,那么返回的状态就是fulfilled;
如果其中有一个状态最终为rejected,那么第一个被rejected的实例返回值将会传递给Promise.all()的回调函数。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

const p3 = new Promise((resolve, reject) => {
  resolve('world');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
Promise.all([p1, p3])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", "world"]

Promise.race()

参数接受与Promise.all()一样。但返回值有所不同,其返回值是最先改变状态的Promise实例的结果。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, new Error("cuowu"));
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 300, 'three');
});
Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
}).catch(err => {
  console.log(err)
});
// Error: cuowu

Promise.race([promise1, promise3]).then((value) => {
  console.log(value);
}).catch(err => {
  console.log(err)
});
// three

Promise.allSettled

用来确定一组异步操作是否都结束了(不管成功或失败)。接受一个数组作为参数,数组的每个成员都是一个 Promise 对象。返回值是一个Promise对象,值是带有描述每个 Promise 结果的对象数组。

只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。

const promise1 = Promise.resolve("hello");
const promise2 = new Promise((resolve, reject) =>
  setTimeout(reject, 100, new Error(报错了))
);

Promise.allSettled([promise1, promise2]).then((results) =>
  console.log(results)
);

// Array [Object { status: "fulfilled", value: "hello" }, Object { status: "rejected", reason: ReferenceError: 报错了 is not defined }]

Promise.any

接受一组 Promise 实例作为参数,返回一个新的 Promise 实例, 结果值为第一个成功的Promise结果。

只要参数实例有一个变成fulfilled状态,返回实例就会变成fulfilled状态;
如果所有参数实例都变成rejected状态,返回实例就会变成rejected状态。

const promise2 = Promise.resolve("hello");
const promise3 = Promise.reject(0);
const res = Promise.any([promise2, promise3]).then((value) => console.log(value));
console.log(res) // [[PromiseResult]]: "hello"

async 与 await

上面把Promise的基本使用和一些方法罗列了。然而其实,开发过程中我们会使用到async function和await来简化编写异步代码。所以这块要放到一起好好结合记录下。

async function创建一个新异步函数,函数体内允许用await关键字。每次调用异步函数都会返回一个Promise对象

await表达式通过暂停执行使返回promise的函数表现得像同步函数一样,直到返回的promise被兑现或拒绝。返回的promise的解决值会被当作该await表达式的返回值。

async function asyncCall() {
  console.log('start');
  const result = await Promise.resolve("promise")
  console.log(result);
  console.log('end')
}

asyncCall();
// 输出 > "start" > "promise"  > "end"

// 不用 async / await
function asyncCall() {
  console.log('start');
  const p = Promise.resolve("promise2")
  p.then(res => {
    console.log(res)
  })
  console.log('end')
}
asyncCall();
// 输出 > "start" > "end" > "promise2"

代码执行结果

最近的一些面试,总会遇到一些笔试,虽然面试的时候能说出个所以然来,但是遇到一些写执行顺序执行结果的,遇上Promise和async/await总是这错那错的(说到底知识还是学得不牢靠),这一块也是涉及到了事件循环、宏任务微任务。记录几道:

#1 热身题

console.log(1)
new Promise((resolve, reject) => {
    console.log(2)
    resolve()
}).then(res => {
    console.log(3)
})
console.log(4)
// > 1 > 2 > 4 > 3
// .then 属于是微任务,需要宏任务执行完之后再执行,所以4比3先输出

#2 加上 setTimeout

console.log("start") // 宏任务 1
setTimeout(() => {
    console.log("setTimeout")
}, 0) // 宏任务 2

new Promise((resolve, reject) => {
    console.log("promise start")
    resolve()
     // 宏任务 1
}).then(res => {
    console.log("promise then")  // 微任务 1
})
console.log("end")  // 宏任务 1
// start > promise start > end > promise then > setTimeout
// 执行顺序:宏任务 1 -》 微任务 -》宏任务2

#3 进阶

console.log("start") // 宏任务1
setTimeout(() => {
    console.log("setTimeout1")
    Promise.resolve().then(() => {
        console.log("1")
    })
}) // 宏任务2
new Promise((resolve, reject) => {
    console.log("2") // 宏任务1
    setTimeout(() => {
        console.log("setTimeout2")
        resolve(2)
    }) // 宏任务3
}).then(res => {
    console.log(res) // 需要等宏任务3执行了才能执行。
    setTimeout(() => {
        console.log("setTimeout3")
    }) // 宏任务4
})
console.log("end") // 宏任务1
// start > 2 > end > setTimeout1 > 1 > setTimeout2 > 2 > setTimeout3

#4 加上 async/await

async function async1() {
    console.log('async1 start')
    await async2() 
    console.log('async1 end') // 等待 async2执行完才执行
}
async function async2() {
    console.log('async2')
}
console.log('script start') // 宏任务1
setTimeout(function () {
    console.log('setTimeout')
}, 0) // 宏任务2
async1() // 宏任务1
new Promise((resolve) => {
    console.log('promise1')
    resolve()
    // 宏任务1
}).then(function () {
    console.log('promise2')
    // 微任务1
})
console.log('script end') // 宏任务1
// script start > async1 start > async2 > promise1 > script end > async1 end > promise2 > setTimeout
// 难点:await会阻塞后面的代码(即加入微任务列表),跳出去执行同步任务。
// 所以async2 后是Promise,而微任务列表则是先执行async1 end后再执行Promise.then部分呢。

#5 变换#4

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    new Promise(function (resolve) {
        console.log('promise1');
        resolve();
    }).then(function () {
        console.log('promise2');
    });
}
console.log('script start');
async1();
new Promise(function (resolve) {
    console.log('promise3');
    resolve();
}).then(function () {
    console.log('promise4');
});
console.log('script end');
// 难点:async1 end 和 promise2执行顺序,async2()的then代码先进入微任务列表,所以先执行promise2再执行async1 end

实现Promise

基于前面基础部分了解,不如自己动手实现更能深入记忆了解。

首先,Promise是一个类,通过构造函数创建实例对象,且接受两个回调函数resolve和reject。 其次,具备三个状态pending、fulfilled、rejected。 最后,实例对象包含:状态、返回值

class MyPromise {
  constructor(executor) {
    this.state = "pending"
    this.result = undefined
    this.errResult = undefined
    // 存储成功和失败的回调方法
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    
    let resolve = (val) => {
      if (this.state === "pending") {
        this.state = "fulfilled"
        this.result = val
        this.onFulfilledCallbacks.forEach(fn => { fn(this.result) })
      }
    }
    let reject = (val) => {
      if (this.state === "pending") {
        this.state = "rejected"
        this.errResult = val
        this.onRejectedCallbacks.forEach(fn => { fn(this.errResult) })
      }
    }
    try {
      executor(resolve, reject)
    } catch(err) {
        reject(err)
    }
  }
}

测试:

const promise1 = new MyPromise((resolve, reject) => {
  resolve("hello")
})
console.log(promise1)
// MyPromise { state: 'fulfilled', result: 'hello', errResult: undefined }
const promise2 = new MyPromise((resolve, reject) => {
  reject(new Error("baocuo"))
})
console.log(promise2)
// MyPromise {state: 'fulfilled',result: undefined, errResult: Error: baocuo}
const promise3 = new MyPromise((resolve, reject) => {
  throw new Error("throw error")
})
console.log(promise3)
//MyPromise { state: 'rejected',result: undefined, errResult: Error: throw error}

接着实现then()catch(),将其放到prototype上,实现共享方法。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : err => {
    throw err
  };
  const newPromise = new MyPromise((resolve, reject) => {
    if (this.state === "fulfilled") {
      setTimeout(() => {
        try {
          let r = onFulfilled ? onFulfilled(this.result) : this.result
          r instanceof MyPromise ? r.then(resolve, reject) : resolve(r)
        } catch(err) {
          // 处理then异常
          reject(err)
        }
      })
    }
    if (this.state === "rejected") {
      setTimeout(() => {
        try {
          let r = onRejected ? onRejected(this.errResult) : this.errResult
          r instanceof MyPromise ? r.then(resolve, reject) : reject(r)
        } catch(err) {
          // 处理then异常
          reject(err)
        }
      })
    }
    if (this.state === "pending") {
      setTimeout(() => {
        onFulfilled && this.onFulfilledCallbacks.push(onFulfilled)
        onRejected && this.onRejectedCallbacks.push(onRejected)
      })
    }
  })
  return newPromise
}
MyPromise.prototype.catch = function(onRejected) {
  setTimeout(() => {
    if (this.state === "rejected") {
      return this.then(undefined, onRejected(this.errResult))
    }
  })
}

测试:

let promise1 = new MyPromise((resolve, reject) => {
  resolve("")
});
promise1.then(res => {
  console.log(res, 'then')
}).catch(err => {
  console.error(err, 'catch')
})
//  then
let promise2 = new MyPromise((resolve, reject) => {
  reject(new Error("?"));
});
promise2.then(res => {
  console.log(res, 'then')
}).catch(err => {
  console.error(err, 'catch')
})
// Error: ? catch

以上简单实现了一个Promise。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表