关于这个问题,一直在纠结要不要处理,因为这个问题在之前只有在微信开发这工具的模拟器中会出现这个问题,在真机环境下一切正常,但是就在前段时间,电脑版微信可以打开小程序了,出于好奇,我把之前做好的小程序,在电脑版微信中打开,而之前所有调用服务端接口的地方都通过Promise进行了二次封装,不试不知道,一试吓一跳,所有promise抛出的异常,无法被捕获到,影响最大的就是业务里面所有登录…拦截失效了,于是联想到应该是和微信小程序2.16.0以上基础库版本无法触发onUnhandledRejection是同一个问题,当然如果你在使用promise的时候,是配合async和await一起使用的,那么是不会有问题的,出于用户有在电脑版微信中运行小程序的需要,才考虑来解决这个问题的,解决了这个问题之后,再次使用电脑版微信打开小程序,一切正常。大致的解决思路是,通过继承原生Promise对其进行重写,并拦截Promise的then已经reject的调用过程,找到Promise链式调用中,未被处理的那个promise,专门针对这个Promise来处理异常。
let p1 = new Promise((resolve, reject) => {
resolve("1234")
})
let p2 = new Promise((resolve, reject) => {
reject("出错了-_-")
})
创建Promise实例时,必须传入一个函数作为参数,这个函数会为我们提供resolve和reject两个参数:
当我们调用resolve时,被创建的promise的状态会由"pending"变为"fulfilled"
而当我们调用reject时,被创建的promise的状态会由"pending"变为"rejected"
可以通过Promise实例的then方法获取当前promise实例的值,then方法接收onFulfilled、onRejected两个参数:
另外Promise还提供了一个catch方法单独指定promise实例的状态由"pending"变为"rejected"时的回调,该方法接收一个onRejected参数,本质上也是调用了then方法,相当于xxx.then(undefined, onRejected)
Promise实例的then方法的调用,会返回一个新的promise,返回的promise取决于onFulfilled、onRejected的调用过程
了解了Promise的基本用法之后,我们知道,可以在Promise的构造方法中拦截promise被创建时resolve和reject的调用过程:
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
excutor((value) => {
resolve(value)
console.log("NativePromiseX resolve", value)
}, (reason) => {
reject(reason)
console.log("NativePromiseX reject", reason)
})
}
}
import { NativePromiseX } from "@/xxxx/native-promisex-blog.js"
Promise = NativePromiseX
let p1 = new Promise((resolve, reject) => {
resolve("1234")
})
let p2 = new Promise((resolve, reject) => {
reject("出错了-_-")
})
可以看到,我们已经能拦截到promise被创建时resolve和reject的调用了,接下来,我们再看一种情况,当我们创建promise时,resolve一个thenable对象:
let p3 = new Promise((resolve, reject) => {
resolve({
then: function(resolve, reject) {
reject("thenable 出错了-_-")
}
})
})
通过控制台的打印可以看到,此时直接把thenable对象作为结果resolve了,而实际的Promise在resolve一个thenable对象时,会把thenable对象的then函数当成一个excutor来处理,因此,正确结果应该打印NativePromiseX reject 出错了-_-
,接下来,咱们来考虑这种情况:
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
excutor((value) => {
try {
this.handleResolve(value, resolve, reject)
} catch(error) {
this.handleReject(error, reject)
}
}, (reason) => {
try {
this.handleReject(reason, reject)
} catch(error) {
this.handleReject(error, reject)
}
})
}
handleReject(reason, reject) {
reject(reason)
console.log("NativePromiseX reject", reason)
}
handleResolve(value, resolve, reject) {
if(value && value.then && (typeof value.then === "function")) {
this.handleThenable(value.then, value, resolve, reject)
} else {
resolve(value)
console.log("NativePromiseX resolve", value)
}
}
handleThenable(then, value, resolve, reject) {
then.call(value, (thenValue) => {
this.handleResolve(thenValue, resolve, reject)
}, (thenReason) => {
this.handleReject(thenReason, reject)
})
}
}
到此我们就可以正常拦截promise的resolve和reject过程了。
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
//当前promise的状态,"pending"、"fulfilled"、"rejected"
this._state = "pending"
this._value = undefined
excutor((value) => {
try {
this.handleResolve(value, resolve, reject)
} catch(error) {
this.handleReject(error, reject)
}
}, (reason) => {
try {
this.handleReject(reason, reject)
} catch(error) {
this.handleReject(error, reject)
}
})
}
handleReject(reason, reject) {
reject(reason)
this._state = "rejected"
this._value = reason
}
handleResolve(value, resolve, reject) {
if(value && value.then && (typeof value.then === "function")) {
this.handleThenable(value.then, value, resolve, reject)
} else {
resolve(value)
this._state = "fulfilled"
this._value = value
}
}
handleThenable(then, value, resolve, reject) {
then.call(value, (thenValue) => {
this.handleResolve(thenValue, resolve, reject)
}, (thenReason) => {
this.handleReject(thenReason, reject)
})
}
}
只需要判断then方法传入的onRejected参数是否为空并且是否是一个函数,如果说是一个函数,则会被处理,否则不会被处理
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
//当前promise的状态,"pending"、"fulfilled"、"rejected"
this._state = "pending"
this._value = undefined
this._handled = false
...
}
...
then(onFulfilled, onRejected) {
if(onRejected && (typeof onRejected === "function")) {
this._handled = true
}
let result = super.then(onFulfilled, onRejected)
return result
}
}
在reject调用之后,判断this._handled是否为false,如果为false,这个promise就是我们要找的那个promise了,我们对他单独进行处理就可以了,但是值得注意的是,这里需要开启一个异步任务去执行
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
//当前promise的状态,"pending"、"fulfilled"、"rejected"
this._state = "pending"
this._value = undefined
this._handled = false
...
}
handleReject(reason, reject) {
reject(reason)
this._state = "rejected"
this._value = reason
setTimeout(() => {
console.log("handleReject tracker", this)
if(!this._handled) {
//this就是那个抛出了一次,但是没有被处理的promise了
}
}, 1)
}
...
}
let p4 = new Promise((resolve, reject) => {
reject("出错了-_-")
})
.catch((reason) => {
return "错误被解决了!"
})
.then((res) => {
return {
then: function(resolve, reject) {
reject("抱歉,又出错了-_-")
}
}
})
.catch((reason) => {
return "错误又被解决了!"
})
.then((res) => {
return Promise.reject("呜呜,还是出错了-_-")
})
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
//当前promise的状态,"pending"、"fulfilled"、"rejected"
this._state = "pending"
this._value = undefined
this._handled = false
...
}
handleReject(reason, reject) {
reject(reason)
this._state = "rejected"
this._value = reason
setTimeout(() => {
console.log("handleReject tracker", this)
if(!this._handled) {
//this就是那个抛出了异常,但是没有被处理的promise了
Promise._onReject(this)
}
}, 1)
}
...
static _onReject(promise) {
//这里只是简单的把错误信息弹出,并且showToast自己做过封装,可在业务中再次封装全局异常处理。。。。
uni.showToast({
title: promise._value,
type: "error"
})
}
}
仔细看上面控制台输出内容,会发现有几个地方报红了,这是因为原生promise默认的rejectionhandle打印的日志,由于我们现在已经能自己去收集未处理的promise异常,并处理它了,所以可以直接把默认的rejectionhandle干掉了。
const noop = function() {}
export class NativePromiseX extends Promise {
...
handleReject(reason, reject) {
reject(reason)
this._state = "rejected"
this._value = reason
this.catch(noop)
setTimeout(() => {
if(!this._handled) {
//this就是那个抛出了一次,但是没有被处理的promise了
Promise._onReject(this)
}
}, 1)
}
...
then(onFulfilled, onRejected) {
if(onRejected && (typeof onRejected === "function") && onRejected !== noop) {
this._handled = true
}
let result = super.then(onFulfilled, onRejected)
return result
}
...
}
const noop = function() {}
export class NativePromiseX extends Promise {
constructor(excutor) {
var resolve
var reject
super((foreignResolve, foreignReject) => {
resolve = foreignResolve
reject = foreignReject
})
//当前promise的状态,"pending"、"fulfilled"、"rejected"
this._state = "pending"
this._value = undefined
this._handled = false
excutor((value) => {
try {
this.handleResolve(value, resolve, reject)
} catch(error) {
this.handleReject(error, reject)
}
}, (reason) => {
try {
this.handleReject(reason, reject)
} catch(error) {
this.handleReject(error, reject)
}
})
}
handleReject(reason, reject) {
reject(reason)
this._state = "rejected"
this._value = reason
this.catch(noop)
setTimeout(() => {
if(!this._handled) {
//this就是那个抛出了一次,但是没有被处理的promise了
Promise._onReject(this)
}
}, 1)
}
handleResolve(value, resolve, reject) {
if(value && value.then && (typeof value.then === "function")) {
this.handleThenable(value.then, value, resolve, reject)
} else {
resolve(value)
this._state = "fulfilled"
this._value = value
}
}
handleThenable(then, value, resolve, reject) {
then.call(value, (thenValue) => {
this.handleResolve(thenValue, resolve, reject)
}, (thenReason) => {
this.handleReject(thenReason, reject)
})
}
then(onFulfilled, onRejected) {
if(onRejected && (typeof onRejected === "function") && onRejected !== noop) {
this._handled = true
}
let result = super.then(onFulfilled, onRejected)
return result
}
static _onReject(promise) {
uni.showToast({
title: promise._value,
type: "error"
})
}
}