20_微信小程序2.16.0以上基础库版本无法触发onUnhandledRejection的替代方案

文章详细介绍了在微信小程序2.16.0及以上版本中,由于无法触发onUnhandledRejection事件导致的Promise异常无法捕获问题,以及如何通过继承并重写Promise来实现异常的拦截和处理,确保在电脑版微信中运行小程序时的稳定性。
摘要由CSDN通过智能技术生成

20_微信小程序2.16.0以上基础库版本无法触发onUnhandledRejection的替代方案

关于这个问题,一直在纠结要不要处理,因为这个问题在之前只有在微信开发这工具的模拟器中会出现这个问题,在真机环境下一切正常,但是就在前段时间,电脑版微信可以打开小程序了,出于好奇,我把之前做好的小程序,在电脑版微信中打开,而之前所有调用服务端接口的地方都通过Promise进行了二次封装,不试不知道,一试吓一跳,所有promise抛出的异常,无法被捕获到,影响最大的就是业务里面所有登录…拦截失效了,于是联想到应该是和微信小程序2.16.0以上基础库版本无法触发onUnhandledRejection是同一个问题,当然如果你在使用promise的时候,是配合async和await一起使用的,那么是不会有问题的,出于用户有在电脑版微信中运行小程序的需要,才考虑来解决这个问题的,解决了这个问题之后,再次使用电脑版微信打开小程序,一切正常。大致的解决思路是,通过继承原生Promise对其进行重写,并拦截Promise的then已经reject的调用过程,找到Promise链式调用中,未被处理的那个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两个参数:

  • onFulfilled: promise实例的状态由"pending"变为"fulfilled"时,会被调用
  • onRejected: promise实例的状态由"pending"变为"rejected"时,会被调用

另外Promise还提供了一个catch方法单独指定promise实例的状态由"pending"变为"rejected"时的回调,该方法接收一个onRejected参数,本质上也是调用了then方法,相当于xxx.then(undefined, onRejected)

Promise实例的then方法的调用,会返回一个新的promise,返回的promise取决于onFulfilled、onRejected的调用过程

二.重写Promise,拦截promise的reject过程

了解了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过程了。

三.记录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
		
		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方法,判断当promise被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调用之后,判断promise是否会被处理

在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("呜呜,还是出错了-_-")
})

在这里插入图片描述

五.处理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了
				Promise._onReject(this)
			}
		}, 1)
	}
	
	...
  
  static _onReject(promise) {
    //这里只是简单的把错误信息弹出,并且showToast自己做过封装,可在业务中再次封装全局异常处理。。。。
		uni.showToast({
			title: promise._value,
			type: "error"
		})
	}
}

在这里插入图片描述

六.去除原生promise默认的rejectionhandle

仔细看上面控制台输出内容,会发现有几个地方报红了,这是因为原生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"
		})
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值