众所周知,Promise是为了解决回调地狱问题而产生的,其主要应用于向发服务端发起请求等异步操。那么,除了异步请求还有其他场景适合使用 Promise 吗?下面我们来共同探讨下。

并发执行的任务

Promise除了用于异步操作,还可以用来记录一个业务流程的状态变化,俗称“状态机”。我们可以使用 Promise 提供的 Promise.all 方法来实现并行执行任务。比如现在有一个表单页,里面有若干多个表单项,只有在所有表单都通过校验才允许用户进行下一步操作。下面我们来提炼关键要素:

  • 校验表单是并发操作(这里不考虑关联表单)
  • 只有所有表单都校验通过才允许下一步操作

当看到这样的业务逻辑时,是不是首先想到的是设置一个 flag 变量,然后通过改变flag变量来表示所有表单是否校验通过,这是最容易想到的实现思路。

  1. let flag = true;
  2. let fields = [];
  3. fields.forEach(field=>{
  4. if(!field.validate()) {
  5. flag = false;//校验不通过
  6. }
  7. })

我们现在来考虑下这么做有没有问题?我们保存了多余的变量,并且这样的代码是不支持远程服务端校验的,如果要支持异步操作需要增加更多的代码量,而且这样的代码也是难以理解和维护的。这个时候我们不妨转换下思路,将每个表单项校验看做是一个 Promise 状态机,当校验通过, Promise 状态为 fullfilled ,校验不通过为 rejected 。现在我们使用Promise重构上面的代码,使每一个表单校验都支持异步操作,并且更加容易理解。

我们可以利用 Promise.all 的特性,只有在所有表单项状态都为fullfilled情况下,才允许执行Promise.all后面的then方法

  1. let p1 = new Promise(function(resolve,rejected) {
  2. //表单1校验通过
  3. if(field.validate()) {
  4. resolve()
  5. } else {
  6. rejected()
  7. }
  8. })
  9. //p2,p3...
  10. Promise.all([p1,p2,p3...]).then(()=>{
  11. //所有表单项都校验通过
  12. })

顺序执行的任务

有时候我们会遇到有一系列任务顺序执行的情况。举个例子,现在有一个购物车的需求,用户必须先下单之后才能进行付款,然后付款之后才可以跳转到订单页面,这个需求就可以用 Promise 顺序执行的逻辑实现,状态流转是这样的:

Promise应用场景 - 图1 我们来提炼下关键需求点:

  • 每一个业务环节需要顺序执行,执行顺序不可变,状态不可逆
  • 上一个执行结果可能需要作为下一步的输入

实现这样的业务逻辑我们同样可以不使用 Promise,但是可能实现过程较复杂。我们知道,系统越是复杂,出错的概率越高,所以我们必须用尽量简单的方法实现以上需求。这里我们将用到 Promise 的顺序执行。
实现Promise顺序执行需求以下两个步骤:

  • 定义一个空的 resolved promise,这个promise只是作为建立链的起点
  • 在一个循环中,通过链中上一个 promise 调用 then() 获得新的 promise,更新promise 变量
  1. //利用forEach实现
  2. function run(tasks=[]) {
  3. let promise = Promise.resolve();
  4. tasks.forEach(task=>{
  5. promise = promise.then((value)=>{
  6. return task(value);
  7. })
  8. })
  9. return promise;
  10. }

  1. //利于reduce实现
  2. function run(tasks=[]) {
  3. let promise = tasks.reduce((prev,task)=>{
  4. return prev.then((value)=>{
  5. return task(value);
  6. })
  7. },Promise.resolve());
  8. return promise
  9. }

兼容callback模式

当然,Promise并不是万金油,不是所有的场合都适合用 Promise。如果是较为简单的逻辑,使用 callback 会更加方便。而且如果我们是在写一个很多人都在用的公共库的话,有些开发者可能不喜欢使用Promise或者对 Promise 不熟悉。这时,我们需要提供一种解决方案,使我们的API可以同时兼容 Promise 和 callback 两种模式。提供一个回调API,回调参数可选,一般情况下使用 callback,在 callback 未传递的情况下使用 Promise。我们可以通过高阶函数实现此功能:

  1. //将返回promise的函数转化为既返回promise又支持
  2. //callback的函数,调用时最后一个参数为callback
  3. function ptc (fn) {
  4. if(typeof fn !== 'function') {
  5. throw new Error("缺少必要参数");
  6. }
  7. return function(...args) {
  8. let callback = args[args.length-1];
  9. let _args = args.slice(0,args.length-1);
  10. return fn.apply(this,args).then(res=>{
  11. if(typeof callback == 'function') {
  12. callback.call(this,null,res)
  13. }
  14. return res;
  15. }).catch(e=>{
  16. if(typeof callback == 'function') {
  17. callback.call(this,e,null)
  18. }
  19. return e;
  20. })
  21. }
  22. }
  23. //测试
  24. a= function(x) {return Promise.resolve(x)}
  25. b=pc(a)
  26. b('xxx',function(err,data){
  27. console.log(err,data)//err:null,data:'xxx'
  28. })
  29. b('asss').then(res=>{
  30. console.log(res);
  31. })