电子记事本 侵删
要实现promise 要遵循以下标准
1.promise是一个提供符合标准的then()方法对象
2.初始对象是pending 能够转化为fulfilled或者rejected对象
3.一旦fulfilled或者rejected状态确定 在也不能转化为别的方法
4.-一旦状态确定 必须返回一个值 并且这个值是不可修改的
产生的原因 解决的问题
javascript是单线程事件驱动的编程语言 要通过回调函数管理多个任务 因为回调函数的滥用 产生了promise
现实业务中依赖关系比较强的回调
// 回调函数
function renderPage() {
const secret = genSecret()
// 获取用户登录态
getUserToken({
secret,
success: token => {
// 获取游戏列表
getGameList({
token,
success: data => {
// 渲染游戏列表
render({
list: data.list,
success: () => {
// 埋点数据上报
report()
},
fail: err => {
console.error(err)
}
})
},
fail: err => {
console.error(err)
}
})
},
fail: err => {
console.error(err)
}
})
}
根据真实的情况 一个回调函数在多个文件中透传
用promise梳理流程后
// Promise
function renderPage() {
const secret = genSecret()
// 获取用户登录态
getUserToken(token)
.then(token => {
// 获取游戏列表
return getGameList(token)
})
.then(data => {
// 渲染游戏列表
return render(data.list)
})
.then(() => {
// 埋点数据上报
report()
})
.catch(err => {
console.error(err)
})
}
单独抽离服用流程后
// 获取游戏列表
// 仅为示例,与实际业务无关
function getGameXYZ() {
const secret = genSecret()
// 获取用户登录态
return getUserToken(token)
.then(token => {
// 获取游戏列表
return getGameList(token)
})
}
// 渲染页面
function renderPage() {
getGameXYZ()
.then(data => {
// 渲染游戏列表
return render(data.list)
})
.then(() => {
// 埋点数据上报
report()
})
.catch(err => {
console.error(err)
})
}
// 其他场景
function doABC() {
getGameXYZ()
.then(data => {
// ...
})
.then(data => {
// ...
})
.catch(err => {
console.error(err)
})
}
实现完整的promiseA+(看git上的js100题
// 记录Promise的三种状态 const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; /** * 运行一个微队列任务 * 把传递的函数放到微队列中 * @param {Function} callback */ function runMicroTask(callback) { // 判断node环境 // 为了避免「变量未定义」的错误,这里最好加上前缀globalThis // globalThis是一个关键字,指代全局对象,浏览器环境为window,node环境为global if (globalThis.process && globalThis.process.nextTick) { process.nextTick(callback); } else if (globalThis.MutationObserver) { const p = document.createElement("p"); const observer = new MutationObserver(callback); observer.observe(p, { childList: true, // 观察该元素内部的变化 }); p.innerHTML = "1"; } else { setTimeout(callback, 0); } } /** * 判断一个数据是否是Promise对象 * @param {any} obj * @returns */ function isPromise(obj) { return !!(obj && typeof obj === "object" && typeof obj.then === "function"); } class MyPromise { /** * 创建一个Promise * @param {Function} executor 任务执行器,立即执行 */ constructor(executor) { this._state = PENDING; // 状态 this._value = undefined; // 数据 this._handlers = []; // 处理函数形成的队列 try { executor(this._resolve.bind(this), this._reject.bind(this)); } catch (error) { this._reject(error); console.error(error); } } /** * 向处理队列中添加一个函数 * @param {Function} executor 添加的函数 * @param {String} state 该函数什么状态下执行 * @param {Function} resolve 让then函数返回的Promise成功 * @param {Function} reject 让then函数返回的Promise失败 */ _pushHandler(executor, state, resolve, reject) { this._handlers.push({ executor, state, resolve, reject, }); } /** * 根据实际情况,执行队列 */ _runHandlers() { if (this._state === PENDING) { // 目前任务仍在挂起 return; } while (this._handlers[0]) { const handler = this._handlers[0]; this._runOneHandler(handler); this._handlers.shift(); } } /** * 处理一个handler * @param {Object} handler */ _runOneHandler({ executor, state, resolve, reject }) { runMicroTask(() => { if (this._state !== state) { // 状态不一致,不处理 return; } if (typeof executor !== "function") { // 传递后续处理并非一个函数 this._state === FULFILLED ? resolve(this._value) : reject(this._value); return; } try { const result = executor(this._value); if (isPromise(result)) { result.then(resolve, reject); } else { resolve(result); } } catch (error) { reject(error); console.error(error); } }); } /** * Promise A+规范的then * @param {Function} onFulfilled * @param {Function} onRejected */ then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this._pushHandler(onFulfilled, FULFILLED, resolve, reject); this._pushHandler(onRejected, REJECTED, resolve, reject); this._runHandlers(); // 执行队列 }); } /** * 仅处理失败的场景 * @param {Function} onRejected */ catch(onRejected) { return this.then(null, onRejected); } /** * 无论成功还是失败都会执行回调 * @param {Function} onSettled */ finally(onSettled) { return this.then( (data) => { onSettled(); return data; }, (reason) => { onSettled(); throw reason; } ); } /** * 更改任务状态 * @param {String} newState 新状态 * @param {any} value 相关数据 */ _changeState(newState, value) { if (this._state !== PENDING) { // 目前状态已经更改 return; } this._state = newState; this._value = value; this._runHandlers(); // 状态变化,执行队列 } /** * 标记当前任务完成 * @param {any} data 任务完成的相关数据 */ _resolve(data) { this._changeState(FULFILLED, data); } /** * 标记当前任务失败 * @param {any} reason 任务失败的相关数据 */ _reject(reason) { this._changeState(REJECTED, reason); } }
简单的promiseall(同样来自js100题
Promise.myAll = (promises) => { return new Promise((resolve, reject) => { let idx = 0; const ans = []; promises.forEach((item, index) => { Promise.resolve(item).then( (res) => { ans[index] = res; idx++; if (idx === promises.length) { resolve(ans); } }, (err) => reject(err) ); }); }); };
在promise出现之前常常使用回调函数管理一些异步程序的状态
常见的异步ajax请求格式
ajax(url, successCallback, errorCallback)
promiss出现后使用then接受事件的状态并且只收一次
使用promise
const PPlugin = {/* Pass */ }
let initOk = null
const ppInitStatus = new Promise(resolve => initOk = resolve)
PPlugin.init = callback => {
ppInitStatus.then(callback).catch(console.error)
}
// 客户端桥接...
// 服务端接口...
// 经历了一系列同步异步程序后初始化完成
initOk(/* 数据 */)
相对与使用回调函数 逻辑清晰 不需要重复判断状态和回调函数 更好的做法是只给输出方输出状态和数据 如何使用由使用方来决定
插件代码
const PPlugin = {/* Pass */ }
let initOk = null
const ppInitStatus = new Promise(resolve => initOk = resolve)
PPlugin.init = callback => {
ppInitStatus.then(callback).catch(console.error)
}
// 客户端桥接...
// 服务端接口...
// 经历了一系列同步异步程序后初始化完成
initOk(/* 数据 */)
使用插件
<!-- 使用方式已变化 -->
<script src="https://example.com/pplugin@latest/pplugin.min.js"></script>
<script>
PPlugin.init.then(callback).catch(console.error)
</script>
链式调用
then方法一定会返回一个promise对象 promise对象又拥有一个then方法 这就是promise链式调用的原因
由于返回一个promise结构体永远返回的是链式调用的最后一个then() 所以在处理封装好的promise接口时没必要在外面在包一层promise
// 包一层 Promise
function api() {
return new Promise((resolve, reject) => {
axios.get(/* 链接 */).then(data => {
// ...
// 经历了一系列数据处理
resolve(data.xxx)
})
})
}
// 更好的做法:利用链式调用
function api() {
return axios.get(/* 链接 */).then(data => {
// ...
// 经历了一系列数据处理
return data.xxx
})
}
管理多个promise实例是使用promiseall 可将多个promise实例封装为一个promise实例
function wait(ms) {
return new Promise(resolve => setTimeout(resolve.bind(null, ms), ms))
}
// Promise.all
Promise.all([wait(2000), wait(4000), wait(3000)])
.then(console.log)
// 4 秒后 [ 2000, 4000, 3000 ]
// Promise.race
Promise.race([wait(2000), wait(4000), wait(3000)])
.then(console.log)
// 2 秒后 2000
promise和async&await
async&await是一个promise的语法糖让一步看起来像同步 所以它们在javascript线程中式非阻塞的
但在当前函数作用域中具备阻塞性质
使用async&await不需要特定创建一个匿名函数
// async / await
async function getUserInfo() {
return await getData()
}
处理条件语句
让可读性变得更好
// Promise
function getGameInfo() {
getUserAbValue().then(
abValue => {
if (abValue === 1) {
return getAInfo().then(
data => {
// ...
}
)
} else {
return getBInfo().then(
data => {
// ...
}
)
}
}
)
}
// async / await
async function getGameInfo() {
const abValue = await getUserAbValue()
if (abValue === 1) {
const data = await getAInfo()
// ...
} else {
// ...
}
}
处理中间值增强可读性
// Promise
function getGameInfo() {
getToken().then(
token => {
getLevel(token).then(
level => {
getInfo(token, level).then(
data => {
// ...
}
)
}
)
}
)
}
// async / await
async function getGameInfo() {
const token = await getToken()
const level = await getLevel(token)
const data = await getInfo(token, level)
// ...
}
多个异步返回中间值 使用promiseall提高逻辑性和性能
// async / await & Promise.all
async function foo() {
// ...
const [a, b, c] = await Promise.all([promiseFnA(), promiseFnB(), promiseFnC()])
const d = await promiseFnD()
// ...
}
await会把任何不是promise的值包装成promise 处理三方接口是可以保持同步状态的异步返回值 否则对于一个非proise返回值使用then链式调用可能会报错
避免滥用
避免在上下文关联不强的代码中使用
// async / await
async function initGame() {
render(await getGame()) // 等待获取游戏执行完毕再去获取用户信息
report(await getUserInfo())
}
// Promise
function initGame() {
getGame()
.then(render)
.catch(console.error)
getUserInfo() // 获取用户信息和获取游戏同步进行
.then(report)
.catch(console.error)
}
top level await 可以在module中使用await操作符
// fetch request
const colors = fetch('../data/colors.json')
.then((response) => response.json())
export default await colors
错误处理
通过BOM操作监听没有处理的promise操作
window的拒绝状态监听事件
-unhandledrejection 当promise被拒绝时 并且没有提供拒绝程序时 触发该事件
-jejecttionhandled 当被拒绝 斌且拒绝程序被调用 触发该事件
// 初始化列表
const unhandledRejections = new Map()
// 监听未处理拒绝状态
window.addEventListener('unhandledrejection', e => {
unhandledRejections.set(e.promise, e.reason)
})
// 监听已处理拒绝状态
window.addEventListener('rejectionhandled', e => {
unhandledRejections.delete(e.promise)
})
// 循环处理拒绝状态
setInterval(() => {
unhandledRejections.forEach((reason, promise) => {
console.log('handle: ', reason.message)
promise.catch(e => {
console.log(`I catch u!`, e.message)
})
})
unhandledRejections.clear()
}, 5000)
主动调用promise.reject()等方法不能直接触发 unhandledrejection事件 必须时已经满足了的then()链式调用事件的promise对象才能
当执行一个超级久的异步请求 超过等待时常就要尝试取消 只能用resolve和reject来改变状态 有开源库满足要求
可以用promise.race来注入一个会超时的异步函数 但是race结束后主程序还是在pending()中 占用的资源还没有解放
Promise.race([anAsyncFn(), timeout(5000)])
可以使用迭代器舒徐执行一堆异步程序 但是超过站调用最大次数会报错
function wasteTime(ms) {
return new Promise(resolve => setTimeout(() => {
resolve(ms)
console.log('waste', ms)
}, ms))
}
// 依次浪费 3 4 5 3 秒 === 15 秒
const arr = [3000, 4000, 5000, 3000]
arr.reduce(async (last, curr) => {
await last
return wasteTime(curr)
}, undefined)
实际使用 登录优化流程
使用promise改写后 简单调用只需要关注状态和流程值
Login.vue
methods: {
async comLogin() {
const userInfo = await State.quickLogin() // 1
// Other Code...
},
},
State.js
async quickLogin() {
// Other Code...
if (this.canQuickLogin) {
return this.ppQuickLogin() // 2
} else {
return getScript('//example.com/ppquicklogin.min.js').then(() => {
// Other Code...
return this.ppQuickLogin() // 2
})
}
}
async ppQuickLogin() {
// Other Code...
return this.getUserState() // 3
}
async getUserState() {
// Other Code...
return this.userInfo
}
使用promise改写确认前置条件减少冗余代码
index.vue
methods: {
async getSwitchStatus() {
// Other Code...
return isSwitchOn
},
async getLoginStatus() {
// Other Code...
return isLogined
},
async pushGame() {
// Other Code...
const [isSwitchOn, isLogined] = await Promise.all([this.getSwitchStatus(), this.getLoginStatus()])
if (isSwitchOn && isLogined) {
// Other Code...
} else {
// Other Code...
}
},
},
总结
每当使用异步代码时考虑使用promise
promise方法所有返回值都是promise
promise中状态改变时一次性的 建议在reject方法中传递error对象
尽量为所有的promise添加then和catch方法
使用promise.all运行多个promise对象
若i昂在then和catch后面做什么 可以用finally
可以将多个then挂在同一个pomise对象上
async函数返回一个promise 所有返回promise的函数可以视作做一个异步函数
await用于调用异步函数 直到其状态改变 fulfilled or rejectd
使用asyc /await 要考虑傻瓜下文的依赖 避免造成不必要的浪费
chat内容梯子不行下次再补