# 背景

最近遇到一个问题,具体的场景是,接口 A 返回一段可以让前端渲染的 html 字符串,这个字符串里包含里一个可播放的 video 的地址,但是地址是不可播放的,需要替换为另外一个可播放的域名。

# 处理流程

解决方式:先请求接口 A 获取 html 字符串,从中截取 videosrc 属性,再调接口 B 将接口转换为可播放的 url,最后拿到新的 url 之后再通过正则的 replace 方法将对应的 url 替换成新的 url,然后再处理剩下的逻辑。

其实捋下来之后发现无非就是 A 的回调结果里调 B 接口,但是即便是嵌套一层,也不够优雅,于是在想利用 Promise 的串行处理,将回调打平,Promise 的处理无非也就是先去调用 A 接口,获取数据之后将结果传递出去再通过.then 拿到返回值数据,在.then 里再调新接口 B 从而解决问题。虽然后来确实通过 Promise 的链式调用解决了问题,但是这个过程中也有一些疑惑,就是如题。

# 拆解和理解

根据 es6resolve 的理解,其函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去,如果我们不 resolve,则数据一直被保存在上一个 Promise 的函数中。经过整理重新学习后发现,如果需要将 Promise 的返回的结果在当前函数的.then 里一直按照次序往下传递,其是必须要在上一步的 then 里将结果 return 才能在下一个 then 里接住,一旦在某一个 thenresolve 了,则在 resolve 的那个 then 后面的 then 中虽然后面的逻辑会执行,但是获取不到上一次计算的结果,反而是直接在 resolvethen 里将结果从当前函数传递出去了。

# 示例

具体用一个 🌰 来看: 假设现在我们有一个 sleepPromise 的模拟异步方法,传递不同的 ms 数代表分别代表 A、B 俩接口。

var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 这是获取新url的接口B
var urlConvert = (url) =>
  new Promise((resolve, reject) => {
    console.log(`我们现在获取到了旧的url:${url} 要去请求新的`);
    sleep(500).then((res) => {
      if (true) {
        resolve("http//newVideoUrl.com");
      } else {
        reject("发生错误");
      }
    });
  });

var handleData = () =>
  new Promise((resolve, reject) => {
    // 接口A
    var initUrl = sleep(1000).then((res) => "http://oldVideoUrl.com");

    initUrl.then((res) => urlConvert(res)).then((res) => resolve(res));
  });

handleData().then((url) => {
  console.log("url", url);
});

这里我们能发现在 handleData 内部,我是需要先处理完数据,在最后才将处理完的数据 resolve,所以可以看到是前边通过箭头函数一步一步将上一次处理的结果 return 了,所以在下一个 then 里才获取到处理完成的数据,并且我们在最后一步通过 resolve 将结果返回,当我们在外部调用 handleData 之后,链式调用的结果是可以获取到 url 的值。

但是如果我们将最后一步结果依旧改成 return 的形式,我们再来看一下效果。

- initUrl.then((res) => urlConvert(res)).then((res) => resolve(res));
+ initUrl.then((res) => urlConvert(res)).then((res) => res);

handleData().then((url) => {
  console.log("url", url);
});

再次运行后,发现没有打印也就是没走到这里,说明如果直接 return 的话是无法从原来的函数里将 Promise 的结果传递出去的。

# 结论

这里基本上是更清楚了,如果不需要将结果传递出去,在 then 处理完成之后 return value 就好了,如果需要传递出去就将数据 resolve(value) 出去,至于 resolve 的时候要不要 加 return 就看还要不要处理 resolve 后面的逻辑,一般来说立即 resolvedPromise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

所以如果上述例子改为

- initUrl.then((res) => urlConvert(res)).then((res) => resolve(res));
+ initUrl.then((res) => urlConvert(res)).then((res) => {
+  resolve(res)
+  console.log('resolve后,打印url前')
+});

便会在 resolve 的那次事件循环后,并且会在新的一轮 handleData() 前执行。

最后更新: 2/12/2023, 7:42:22 AM