bilibili文章学习 promise

电子记事本 侵删

要实现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内容梯子不行下次再补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值