Promise对象 3 种妙用指南攻略_Promise指南攻略

作为一个前端,说不了解 Promise 对象用法的基本不存在,这里就不对功能用法进行介绍了。但本文将会讲述你可能不知道的 Promise 3 种奇妙用法。当然,每种用法都会有其适用的特殊场景。Promise 对象是可以缓存需求对于一个对象而言,能够被缓存并不是一件难以理解的事情。缓存使用的意义往往是为了解决性能问题。而对于一个特定请求的 Promise 对象

Promise对象 3 种妙用指南攻略

作为一个前端,说不了解 Promise 对象用法的基本不存在,这里就不对功能用法进行介绍了。但本文将会讲述你可能不知道的 Promise 3 种奇妙用法。当然,每种用法都会有其适用的特殊场景。

Promise对象 3 种妙用指南攻略_Promise指南攻略

Promise 对象是可以缓存

需求

对于一个对象而言,能够被缓存并不是一件难以理解的事情。缓存使用的意义往往是为了解决性能问题。而对于一个特定请求的 Promise 对象而言,缓存的意义在于同时多个组件的使用该请求,会因为请求未返回而进行多次请求。一图胜千言,图示如下:

Promise对象 3 种妙用指南攻略_Promise指南攻略

因为在某些特定需求或者场景下(甚至因为团队的因素),某个组件在可以在页面单独使用,也可以结合其他组件共同使用。若此时多个组件都需要对某个通用数据进行请求,就会发生多次请求,对性能不利。但如果全部移植到父组件去请求,又是需要一顿操作,对开发不爽。

解决方案

所以这时候我们基于 api 与 请求参数加缓存。先写一个生成 key 的函数(此函数仅仅只适用简单的请求参数,不适合对象等复杂数据结构,因为是通用型数据,不考虑太复杂的请求参数,如有需求可以自行改造)。

// 生成key值错误
const generateKeyError = new Error("Can't generate key from name and argument")

// 根据当前的请求参数生成 key 值
function generateKey(name, argument) {
    // 从arguments 中取得数据然后变为数组
    const params = Array.from(argument).join(',')
    
    try{
        // 返回 字符串,函数名 + 函数参数
        return `${name}:${params}`
    }catch(_) {
        // 返回生成key错误
        return generateKeyError
    }
}

下面是数据请求缓存,不过令人觉得可惜的是: 数据请求缓存并不能解决多次请求的问题。

const dataCache = new Map()

async getxxx(params1, params2) {
   const key = generateKey('getxxx', [params1, params2]) 
    // 从data 缓存中获取 数据
    let data = dataCache.get(key)
    if (!data) {
        // 没有数据请求服务器
        const res = await request.get('/xxx')
        
        // 其他操作
        ...
        data = ...

        // 设置数据缓存
        dataCache.set(key, data)

    }
    return data
} 

因为虽然 js 是单线程的,所以在第二个以及以上的组件请求时候,会因为请求未返回而进行再次请求 api。流程如下:

  • a 组件请求
  • dataCache.get == null
  • 建立请求(等待返回)
  • 其他操作
  • b 组件请求
  • dataCache.get == null
  • 建立请求(等待返回)
  • 其他操作
  • …. …
  • 放入缓存且返回数据
  • 放入缓存且返回数据
  • …. …

如果缓存的是 Promise 对象,则该方案可以解决问题。

const promiseCache  = new Map()

async getxxx(params1, params2) {
   const key = generateKey('getxxx', [params1, params2]) 
    // promiseCache 缓存中获取 缓存
    let xxxPromise = promiseCache.get(key);
    // 当前promise缓存中没有 该promise
    if (!xxxPromise) {
        xxxPromise = request.get('/getxxx').then(res => {
            // 对res 进行操作
            ...
        }).catch(error => {
            // 在请求回来后,如果出现问题,把promise从cache中删除 以避免第二次请求继续出错
            promiseCache.delete(key)
            return Promise.reject(error)
        })
        promiseCache.set(key, promise)
    }
    return xxxPromise
} 

流程如下:

  • a 组件请求
  • promiseCache.get == null
  • 建立请求
  • 返回 promise
  • 其他操作
  • b 组件请求
  • promiseCache.get != null
  • 返回 promise
  • 其他操作
  • …. …

同时,因为 promise 是异步操作,所以在发生错误时候 catch 中去除缓存以便于缓存了错误的promise。

进一步了解与学习

该方案可以减轻同一时间多次请求同一数据所带来的性能问题。

如果你还想结合过期时间与装饰器来对缓存进行赋能,可以参考我之前的博客文章前端 api 请求缓存方案

Promise 可以封装大量异步操作

需求

在写关于异步请求时候,通常是基于请求直接返回 api 请求响应数据,对其进行正常和错误处理。当时多次异步操作从而返回正确与错误的流程却很少进行梳理。如果在一次请求内有多个异步操作:代码就会变得难以维护。

解决方案

学习 Promise 时候,往往会与有限状态机结合在一起说,如果你实现过 Promise,你就清晰的知道: 如果内部没有状态没有发生变化,可以执行大量异步操作。体现为如果没有调用 resolve 或者 reject 函数,则不会对于当前 Promise 的状态和值进行修改,也就不会执行后面的链式调用。

// 异步操作封装
function asyncOpt(opt: any) {
  return new Promise((resolve, reject) => {
    // 传入的 opt 异步操作
      
    // 如 请求失败,失败的逻辑判断后再次请求   
    // 又如多个 异步操作, 在最后一个异步操作成功后执行
      
    reslove(result)


    // 多个 异步操作中的 catch, 在每个错误中执行
    reject(error)
  })  
}


asyncOpt(data).then(result => {
  // 正常流程
}).catch(error => {
  // 错误流程
})

写出如上的代码,就可以在很多业务项内进行操作,诸如某些操作有前置权限请求,或者某些错误代码需要重新请求或者埋点等操作。

进一步了解与学习

如果觉得上述的例子不够复杂,不够体现出 Promise 封装的妙用,你可以研究关于微信登陆态的管理。事实上,在没有知道这种用法之前,确实没有很好的办法解决这种问题。

Promise对象 3 种妙用指南攻略_Promise指南攻略

当然,github 上已经有了开源实践 weRequest,该库实现了无感知登陆,且代码风格与结构非常值得学习,可以参考我之前的博客文章 从 WeRequest 登陆态管理来聊聊业务代码。

同时,可以封装异步操作可并不仅仅只是指代异步请求,如果是你使用过Element confirm,一定对如下代码不陌生。

this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
  confirmButtonText: '确定',
  cancelButtonText: '取消',
  type: 'warning'
}).then(() => {
   this.$message({
    type: 'success',
    message: '删除成功!'
   });
}).catch(() => {
   this.$message({
     type: 'info',
     message: '已取消删除'
   });          
});

这样的话,不需要再界面上写 confirm 以及一些控制显隐的代码,基于配置(字符串) 触发 promise 开始显示后销毁。

  • $msgbox(options)
  • $alert(message, title, options) 或 $alert(message, options)
  • $confirm(message, title, options) 或 $confirm(message, options)
  • $prompt(message, title, options) 或 $prompt(message, options)

最后结合全局方法和 渲染函数 甚至也可以实现 Modal 配置化,传入组件,配置以及数据。可以类似于如下写法(当然,事实上用不用 Promise 都可以实现该方案,只不过 Promise 的状态转化很适合,与其自己实现一个状态机,倒不如使用promise):

this.$modal(xxxComponent, componentConfig, propConfig).then(result => {
    // 根据不同返回结果来处理
}).catch(reason => {
    // 取消处理方法
})
// 甚至还可以加 finally 方法

Promise 泄露触发转化方法

需求

最近有小伙伴来找我询问,如何解决后一个请求比前一个请求还要快,因为他写了输入实时查询的功能。我直接让他使用防抖函数,但是他告诉我他已经使用了 500ms 的防抖但是服务端仍旧是会存在问题。

本来考虑再前一个请求成功后再进行下一次,但是考虑到这个方案会慢点很明显,后面考虑请求唯一化,但是因为使用 axios 做请求库,该请求并不特殊,特殊化处理明显是增加了代码复杂度,也是不太好。

解决方案

后面他告诉我,他已经解决了此问题,因为 axios 有一个方法可以取消请求。也就是如果他进行下一个请求,便会取消上一个请求。下面代码是官方示例:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

其实我是知道 Promise 中是有 cancelble 提案,但是该提案在第一阶段就因为被谷歌的强烈反对而取消了,那么我就去看 axios 源码来看一看如何实现取消。下面代码在 xhr.js 中。

// 如果配置出现 cancelToken
if (config.cancelToken) {
  // Handle cancellation
  // 设定 处理取消方法
  config.cancelToken.promise.then(function onCanceled(cancel) {
    // 请求被置空,直接返回,以避免出错
       if (!request) {
      return;
    }
    // xhr.abort 取消请求
    request.abort();
    // 执行 reject  
    reject(cancel);
    // 请求置空
    request = null;
  });
}

先谈谈 abort 函数,abort 是 xhr 对象中的方法,根据 mdn :

这个请求指的是 http 请求,这样就会出现一个问题,基于http请求原理,当一个请求从客户端发出去之后,服务器端收到请求后,一个请求过程就结束了,这时就算是客户端abort这个请求,服务器端仍会做出完整的响应,只是这个响应客户端不会接收。所以实质上,后端还是处理了请求,但是前端不对该方法进行处理。

其中 promise 取消 核心代码如下 CancelToken.js。

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }
  // 设定 resolvePromise
  var resolvePromise;
    
  // xhr config.cancelToken.promise.then 就是当前的 promise 
  this.promise = new Promise(function promiseExecutor(resolve) {
    // 设定 和导出 resolve
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

      
    token.reason = new Cancel(message);
    // 执行 resolvePromise
    resolvePromise(token.reason);
  });
}

/**
 *  对应使用中    source
 *  axios.get('/user/12345', {
 *    cancelToken: source.token
 *    })
 *    source.cancel('Operation canceled by the user.');
 */
CancelToken.source = function source() {
  var cancel;
  // 返回 cancel token 对象  
  var token = new CancelToken(function executor(c) {
    // 利用 excutor 来把取消函数导出来。也就是 CancelToken excutor函数
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

可以看到 axios 的代码关系还是有一定的复杂度。当然也是因为当前 Promise 没有办法像 setTimeout 等一些方法在调用时候直接返回取消函数,所以不得不借助另一个 promise 异步来处理。同时,也是把复杂度留给了自己,所以还是需要多读几遍。 调用关系如下。

CancelToken 设定了取消的 promise 调用关系, xhr 在有请求配置 cancelToken 的情况下,将当前请求注入的 cancelToken 中的 then 结合,使得调用了 cancel 后可以直接改变 xhr 内部状态。

进一步学习

当然在频繁的页面跳转,同时还有定时请求时候,跳转中的数据请求实际上意义不大: 可以参考 vue axios请求 取消上一个页面所有请求 批量取消请求

取消请求问题其实较为小众的,大部分是可以从请求源头来解决的,同时也因为对于服务端的处理并没有减轻,所以事实上不处理其实倒也没什么问题。但是其中也遇到过小程序中有一些全局的服务,在请求完成后由于触发不到页面数据而报错的问题。

虽然是小众问题,但是遇到该特定场景需要提供解决方案。

同时对于上面的异步操作组件来说,泄露出 resolve 和 reject 函数以便于直接执行 then 或者 catch 是否有意义,又或者中途改变异步组件的实现流程是否真的对业务有所帮助也是值得思考的。

博客地址

海计划公众号
(0)
上一篇 2020/03/20 23:49
下一篇 2020/03/20 23:49

您可能感兴趣的内容

  • Parcel攻略教程快速,零配置的 Web 应用程序打包器

    Parcel基础入门 官方网址:http://www.css88.com/doc/parcel/ GitHub:https://github.com/parcel-bundler/…

    2020/03/05
  • 网页首屏性能优化总结入门攻略_优化入门基础教程

    写在前面作为一名前端工程师,网页首屏性能优化是一个绕不开的话题。最近这段时间我做了一些首屏性能优化的项目,这里做一个小小的总结。项目实施过程网页是运行在浏览器端的,优化网页性能无法脱离浏览器。所以首先需要搞清楚浏览器加载一个网页的过程。最经典、常见的一个问题就是:从输入Url到网页呈现在用户面前,到底发生了什么。这里面最重要的两个点:网络请求过程以及浏览器的

    2020/03/29
  • javascript如何判断复选框是否选中?菜鸟攻略_复选框入门基础

    JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言。复选框对象具有一个checked属性,如果属性值等于true,那么说明选中,如果等于false,则说明没选中。利用javascript实现判断checkbox复选框是否被选中:<!

    2020/03/20
  • javascript怎么判断是否为json?菜鸟知识_json入门知识

    JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。1、使用正则表达式判断是否是jsonif (/^[\],:{}\

    2020/03/20
  • Chrome浏览器语音自动播放功能入门百科_浏览器零基础入门

    Chrome浏览器为了屏蔽带声音的骚扰广告,从66版本后不再允许自动播放语音,我做的项目需要实时语音提示报警信息,网上搜索了好久都说不再支持自动播放,知道碰到一个大神提供建议设置Chrome浏览器允许声音自动播放:在chrome地址栏中输入chrome://flags/再搜索Autoplay policy再在右侧的选项中设置为 No user gesture

    2020/03/20
  • 一份完整的MySQL开发规范,进大厂必看!基础指南_规范入门攻略

    一、数据库命令规范1、所有数据库对象名称必须使用小写字母并用下划线分割2、所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)3、数据库对象的命名要能做到见名识意,并且最后不要超过32个字符4、临时库表必须以tmp_为前缀并以日期为后缀,备份表必须以bak_为前缀并以日期(时间戳)为后缀5、所有存储相同数据的列

    2020/03/26
  • Keyboard小白帮助_处理键盘输入、支持简单的组合按键并且支持不同LOCALE的js库

    Keyboard零基础入门 官方网址:http://mottie.github.io/Keyboard/ GitHub:https://github.com/Mottie/Keyb…

    2020/03/06
  • Dust.js使用教程_一个JavaScript模板引擎

    Dust.js使用教程 官方网址:http://www.dustjs.com/ GitHub:https://github.com/linkedin/dustjs 简介描述:一个J…

    2020/03/06
  • 怎样获取当前网页的URL?基础知识入门_url小白常识

    1. document.documentURIdocument.documentURI;
    // “https://i.cnblogs.com/EditPosts.aspx?opt=1” 2. document.URLdocument.URL;
    // “https://i.cnblogs.com/EditPosts.aspx?opt=1″注意:1. 两个属性的

    2020/03/26
  • DDOS攻击常见的类型入门攻略_攻击使用攻略

    “互联网”指的是全球性的信息系统,是能够相互交流,相互沟通,相互参与的互动平台。随着互联网的飞速发展,越来越多的网站应运而生,但各种问题也随之而来。其中最严重的莫过于网络安全问题,应该象每家每户的防火防盗问题一样,做到防范于未然。甚至不会想到你自己也会成为目标的时候,威胁就已经出现了,一旦发生,常常措手不及,造成极大的损失。而导致这个问题的最主要的因素就是以

    2020/03/29
  • 为什么使用AngularJS?小白常识_Angular入门百科

    Angular.js是google开发者设计和开发的一套前端开发框架,帮助你简化前端开发的负担。下面给大家介绍一下你应该使用Angular.js的重要原因:原因一:Google开发的框架要知道开源界的很多框架都是开发人员由于个人兴趣或者激情而开发出来的,比如,Cappucino 还有 Knockout。而anguar.js是由互联网巨人Google组织开发的

    2020/03/20
  • 电脑cmd命令大全:最全DOS的CMD命令,程序员必会小白常识_cmd入门攻略

    CMD命令:开始->运行->键入cmd或command(在命令行里可以看到系统版本、文件系统版本)1. appwiz.cpl:程序和功能 2. calc:启动计算器 3. certmgr.msc:证书管理实用程序 4. charmap:启动字符映射表 5. chkdsk.exe:Chkdsk磁盘检查(管理员身份运行命令提示符) 6. cleanmgr: 打开

    2020/03/29
  • 叮当设计小白攻略_分享优秀设计资源

    叮当设计小白攻略 官方网址:http://www.dingdangsheji.com 简介描述:分享优秀设计资源 叮当设计网致力于分享优质设计资源,提供PPT模板、PSD源文件、海…

    2020/03/06
  • PhotoSwipe小白攻略_为移动触摸设备设计的相册/画廊

    PhotoSwipe小白攻略 官方网址:http://photoswipe.com GitHub:https://github.com/dimsemenov/PhotoSwipe …

    2020/03/06
  • PM2使用帮助 Node 应用的进程管理器_pm2菜鸟教程下载

    pm2是一个带有负载均衡功能的 Node 应用的进程管理器。当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完美的。它非常适合IaaS结构,但不要把它用于PaaS方案(随后将开发Paas的解决方案)。主要特性:内建负载均衡(使用Node cluster 集群模块)后台运行0秒停机重载具有Ubuntu和CentO

    2020/03/26
  • 前瞻网入门基础_一个产业研究型资讯服务平台

    前瞻网入门基础 官方网址:https://www.qianzhan.com/ 简介描述:一个产业研究型资讯服务平台 前瞻网隶属于深圳前瞻资讯股份有限公司管理运营,是中国产业科技深度…

    2020/03/06