Promise在项目中的使用技巧

356 阅读6分钟

1.微信小程序中共享登录状态

通常用户打开进入小程序之后会进入到我们的 index 首页,但是也有情况会是通过朋友分享的链接进入小程序,而分享的链接则大概率非首页。在多个页面调用登录显然是不合理的。

那么登录通常是会放在 app.js 中的 onLaunch 方法,而这也是进入小程序首先会被执行的钩子函数。接着会执行 page 页面的 onLoad 方法。这个过程是同步跟着后面调用的。而与此同时登录是异步请求,也就是说页面的 onLoad 方法被调用时登录并没有完成。

那首先想到的是有没有办法在 app.js 中类似告诉后面的 page,等我这里登录完成后你再载入执行你的 onLoad 方法。这里微信小程序没有这种方法!

这里可以用 promise 解决这个问题,直接上代码:

//app.js
App({
  onLaunch() {
    this.globalData.loginState = new Promise((resolve, reject) => {
      wx.login({
        success: (res) => {
          // 此处可以加上调用自己项目中服务器端拿回来的用户数据data
          resolve(data);
        },
        fail: (res) => {
          reject();
        },
      });
    });
  },

  globalData: {
    loginState: null,
  },
});
//index.js

var app = getApp();
Page({
  onLoad() {
    this.globalData.loginState
      .then(() => {
        // ... 成功的业务逻辑
      })
      .catch(() => {
        // ... 失败的业务逻辑
      });
  },
});

简析: app 中的 onLaunch 首先会被执行,传入的函数会被同步调用,同时登录请求会被同步发起。这时 loginState 就是一个 promise 。我们知道 promise 具有状态性,且一旦状态变化之后不可再变化。 其实 page 中的业务逻辑代码并不关心 loginState 此刻的状态是等待还是被决议(包括成功和失败),因为如果是 pending,那我等你就是了,如果是决议了,同样我进行后续的不同处理。值得注意的是一个 promise 是可以多次调用 then 的,这表示在其它 page 里同样的 onLoad 代码,它依然可以正常工作。不论这个 page 是首次进入的页面,还是用户点击跳转后进入的页面。因为我的主体都是 loginState 这个 promise 。

2. 关于操作权限的统一控制判断

需求:

  • 用户分为会员用户和非会员用户。
  • 会员用户可以不限次数的进行下载视频的功能
  • 非会员用户具有下载视频的体验次数,每次使用则弹窗提示剩余体验次数。

对该功能的校验方法的封装:

async function permissionHandler() {
  const { content } = await api();
  // 会员用户
  if (content.isVip) {
    return Promise.resolve();

    // 非会员用户
  } else {
    // 有体验次数
    if (content.times) {
      return MessageBox.confirm(`剩余体验次数${content.times}`);

      // 没有体验次数
    } else {
      return Promise.reject();
    }
  }
}

// 调用
permissionHandler()
  .then(() => {
    // 下载视频
  })
  .catch(() => {
    // 不下载视频
  });

此处使用 async 函数,它也是基于 promise。首先 await 一个异步请求,当前用户是否是会员用户,如果是会员则直接返回一个状态是成功的 promise,要知道在 async 函数里,返回值必然会是一个 promise,此处你也可以直接 return 省略 Promise.resolve(),因为即便不写具体的返回值,函数默认返回 undefined,那么此处经过包装等同于 return Promise.resolve(undefined),我的建议是总是显示的写出它,便于理解维护。

接下来另一种情况走到非会员处,如果没有体验次数则直接返回一个失败状态的 promise,原理同上。而如果有体验次数,返回的是 MessageBox.confirm(), 它是一个等待状态的 promise,既然状态正在等待中,那么我的业务逻辑下不下载视频就还未确定。问题是何时确定,恰恰就在弹窗后的点击确定按钮还是取消按钮。点击确定后 MessageBox.confirm()则从等待变为成功,随即就会执行我的下载视频的业务逻辑,点击取消则相反。

3. 提交表单

  • 提交表单前敏感词校验
function sensitiveWordCheck() {
  return (
    apiSensitive()
      //验证通过没有敏感词
      .then(() => {
        return Promise.resolve();
      })
      //有敏感词
      .catch(() => {
        return Promise.reject();
      })
  );
}

function submit() {
  sensitiveWordCheck()
    .then(() => {
      // 提交
      return apiSubmit();
    })
    .then(() => {
      // 提交成功
    })
    .catch(() => {
      // 提交失败
    });
}

submit();

首先 submit()中我希望是干净的主线逻辑,所以敏感词校验写成了一个方法 sensitiveWordCheck(),因为它需要包含 api 的调用(敏感词是把内容交给后端判断的)即 apiSensitive(),还需要包含对有敏感词的过滤处理。所以有关敏感词的东西我都不希望出现在 submit 方法中,这会让我的主线变得不清晰。

现在来细看 sensitiveWordCheck()代码,第一行 return 的是什么,化简一下就是:

function sensitiveWordCheck() {
  return apiSensitive().then().catch();
}

要知道 apiSensitive()是异步请求返回一个 promise,接着调用了 then 之后返回的还是 promise,既然是 promise 那可以继续调用 catch,这时返回的依然是 promise。所以整体 return 的是调用 catch 之后的 promise。要知道我们拿到一个 promise 第一件事情应该想的是他是什么状态,然后进行接下来的分析和处理。先来看 apiSensitive()的不同状态变化的情况

  1. pending -> fulfilled

此时是成功态表示没有敏感词,可以继续往下走提交表单,此种情况下面几种代码效果是一样的:

function sensitiveWordCheck() {
  return apiSensitive()
    .then(() => {
      return Promise.resolve();
    })
    .catch(() => {});
}
// 等同于
function sensitiveWordCheck() {
  return apiSensitive()
    .then(() => {
      return; //这里可以省略return
    })
    .catch(() => {});
}
// 等同于  (甚至可以直接省略then如果你没有其它需要处理的逻辑)
function sensitiveWordCheck() {
  return apiSensitive().catch(() => {});
}

then 的回调里如果没有返回一个 promise, 那么同样它会返回一个默认值 undefined,经过包装就是返回 Promise.resolve(undefined),当然如果回调里有报错就另当别论了。

  1. pending -> rejected

如果是失败态则会走到 catch 中,虽然 then 方法有第二个回到函数表示失败状态的回调,但是我的习惯是总是用 catch 来捕获错误,那么这里要知道一个 promise 被 catch 捕获了,就表示错误被处理掉了。如果下面继续有 then 的话,那它会被执行。除非在 catch 里返回一个失败的 promise。在我们的例子中有显示的写return Promise.reject();就是在告诉后面这里失败了,即便我这里捕获了这个错误,因为我可能有其他的处理。比如我加一点逻辑:如果有敏感词,弹出一个对话框,询问是否需要把敏感词过滤掉,如果选确定则帮他过滤,下面继续提交表单,否则相反不过滤不提交

<script>111</script>
function sensitiveWordCheck() {
  return (
    apiSensitive()
      //有敏感词
      .catch(() => {
        return MessageBox.confirm(`是否自动过滤敏感词?`)
          .then(() => {
            // ...自动过滤敏感词的逻辑
            return Promise.resolve()
          })
          // 此处的catch可以省略如果没有额外的逻辑,相当于把错误继续传递到后面
          .catch(() => {
            return Promise.reject()
          })
        ;
      })
  );
}

看到这里可以这么理解catch跟then几乎也是一个道理,catch之后依然是一个promise,那他的状态由什么决定,就是由catch里面的回调返回值来决定,这和then是一样的。