Javascript 异步编程之Promise

对 Javascript 异步编程略有研究的同学会知道, Javascript 异步编程经历了三个阶段,分别是:Callback Hell、Promise 和 async/await。Callback Hell 是最古老的方式,它使用嵌套回调函数的方式实现异步编程,这种方式的函数嵌套层级可能会很深,它的代码结构看上去如下图:

为了解决 Callback Hell 带来的问题,Promise 应运而生。为了方便后续的理解,先介绍一些与 Promise 相关的术语。

术语

thenable 和 non-thenable

  • thenable:有 then 方法的对象,也可以称为 PromiseLike。
  • non-thenable:没有 then 方法的对象

状态

promise 有三种互斥的状态:

1.fulfilled:已经被兑现了。如果 promise 的状态变成 fulfilled,那么 promise.then(f) 中的 f 会被调用。
2.rejected:已经被拒绝了。如果 promise 的状态变成 rejected,那么 promise.then(undefined, r) 中的 r 会被调用。
3.pending:promise 的初始状态,promise 的状态既不是 fulfilled 也不是 rejected。

你可能会听到有人说某个 promise 已经被 settled 了,他的意思是这个 promise 不是 pending 状态,它是 fulfilled 或者 rejected 状态。settled 不是 promise 的状态,它只是语言上的便利。

promise 的状态一旦确定就不可变更,示意图如下:

通过下面的方式可以改变 promise 的状态,简化代码如下:

const myPromise = new Promise((resolve, reject) => {//在此之前 promise 的状态是 pendingresolve(someValue)// line A。promise 的状态会变成 fulfilled?maybe// orreject("failure reason")// lineB。promise 的状态变成 rejected
}); 

你可能已经发现,lineA 比 lineB 的注释多了一个问号。调用 reject 一定会让 myPromise 的状态变成 rejected,但是调用 resolve 不一定会让 myPromise 的状态变成 fulfilled。那么调用 resolve 之后,myPromise 的状态会变成什么呢?这取决于 someValue 的类型。

  • 如果 someValue 是一个 non-thenable,那么 myPromise 的状态会变成 fulfilled
  • 如果 someValue 是一个 thenable 值,那么 myPromise 的状态会跟随 someValue 的状态

命运

promise 有两种互斥的命运:

1.resolved:已解决。如果一个 promise P 的状态跟随另一个 promise Q 的状态、P 的状态变成 fulfilled 或者 P 的状态变成 rejected,当出现这三种情况中的任意一种,那么就能认为 P 被 resolved。
2.unresolved:未解决。

Promise 构造函数

使用 Promise 构建函数是创建 promise 对象的常用方式。Promise 构造函数的接口如下:

interface PromiseConstructor { new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;// something
} 

用 Promise 构建函数创建一个 promise 对象,在这里记为 P。通过上面的 PromiseConstructor 接口,我们可以得出如下结论:

1.Promise 构建函数接受一个函数作为参数,在这里记为 executor,executor 没有返回值。即使你在代码中写了返回值,返回值也会被忽略。
2.executor 的参数 resolve 和 reject 依然是函数。
3.reject 接受任意类型作为参数,它会将 P 的状态变成 rejected。
4.resolve 的行为比 reject 的行为要复杂一些。如果 resolve 的参数的类型是 PromiseLike,例如是另一个 promise 对象,在这里记为 Q,那么 Q 会被动态插入到 promise 链中,这时 P 的状态会跟随 Q 的状态。如果 resolve 的参数的类型不是 PromiseLike,那么 P 的状态会变成 fulfilled。

补充:结合术语部分介绍的内容,我们总结一下。当调用 resolve 或者 reject 之后,我们可以说 P 被 resolved,但是我们不能说 P 的状态是 settled

分析下面这段代码,并在浏览器中运行它,看看你分析的结果与浏览器实际的结果是否一致

function getMyPromise(p, finish = false) {return new Promise((resolve, reject) => {setTimeout(() => {if (finish) {resolve(p)} else {reject(p)}}, 1000)})
}

const myPromise1 = getMyPromise('我的状态会变成 rejected', false)
const myPromise2 = getMyPromise(myPromise1, true)

myPromise2.then((val) => {console.log(`我被兑现了,并且兑现的值是: ${val}`)
}).catch((error) => {console.log(`我被拒绝了, 理由是: "${error}"`)
}) 

浏览器打印的结果是:我被拒绝了, 理由是: “我的状态会变成 rejected”。从浏览器打印结果中可以看出:myPromise2 的状态跟随了 myPromise1 的状态,所以说,promise 的状态是变成 fulfilled 还是 rejected,这与调用 resolve 函数还是调用 reject 函数没有必然关系。

promise 链

现在我们将仔细研究 promise 是如何被链接的。比如,有下面这一行代码:

const Q =P.then(onFulfilled, onRejected) 

Q 是一个新的 promise 对象,所以你能继续在 Q 上调用 then(…):

  • Q 会被 P.then 中 onFulfilled 或 onRejected 的返回值解决。
  • 如果 P.then 中的 onFulfilled 或 onRejected 抛出异常,Q 的状态会变成 rejected。

返回 non-thenable 类型的值

这种方式比较简单,它指的是,在 onFulfilled 或 onRejected 中返回一个 non-thenable 类型的值,该返回值可以在后续的 then 中获取到,Q 的状态会马上变成 fulfilled。

P
.then(()=> {return '123'
})
.then((value) => {console.log(value); // 123
}) 

返回 thenable 类型的值

在 onFulfilled 或 onRejected 中返回一个 thenable 类型的值,比如,在 onFulfilled 中返回 promise R,R 会被插入到 promise 链中。可以用这一特性来中断 promise 链。

利用这一特性,改写下面的代码

asyncFunc1()
.then(function (value1) {asyncFunc2().then(function (value2) {···});
}) 

改写成:

asyncFunc1()
.then(function (value1) {// 如果 asyncFunc2() 返回的 thenable 一直是 pending 状态,那么程序进不到下一个节点。 return asyncFunc2();
})
.then(function (value2) {···
}) 

promise 链与错误捕获

例如有下面两段代码:

asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function (reason) {// 捕获错误
}); 

上面的代码使用 catch 捕获错误

asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3, function (reason) {// 捕获错误
}) 

上面的代码使用 then 的第二个参数 onRejected 捕获错误。

这两种捕获方式有什么差异呢?答案是,catch 能够捕获到 asyncFunc2 和 asyncFunc3 中发生的错误,onRejected 只能捕获到 asyncFunc2 发生的错误。我们改写第二段程序

asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.then(null, function (reason) {// 捕获错误
}) 

现在 onRejected 可以捕获 asyncFunc2 和 asyncFunc3 中发生的错误。

总结:catch 和 then onRejected 都只能捕获它所在节点前面发生的错误。

使用 Promise 链的常见错误

错误一:丢失 promise 链的尾巴

错误的做法

function foo() {const promise = asyncFunc();promise.then(result => {// 这里抛出的异常,在 foo().catch(...) 和 foo().then(null, onRejected) 中捕获不到···});return promise;
} 

正确的做法

function foo() {return asyncFunc().then(result => {···});
} 

错误二:promise 嵌套

不推荐的做法

asyncFunc1()
.then(result1 => {asyncFunc2().then(result2 => {···});
}); 

推荐的做法

asyncFunc1()
.then(result1 => {return asyncFunc2();
})
.then(result2 => {···
}); 

错误三:创建新的 promise 对象而不使用已经存在的 promise 链

不推荐的做法

function asyncFunc2() {return new Promise((resolve) => {asyncFunc1().then(resolve)})
} 

推荐的做法

function asyncFunc2() {return asyncFunc1()
} 

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

猜你喜欢

转载自blog.csdn.net/web220507/article/details/129771470