巧妙利用await、promise中断函数执行(elementui的tree组件实现懒加载与全选功能)

692 阅读3分钟

promise和await

我们先复习一下promise和await的介绍,

promise:一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者(Promise - JavaScript | MDN (mozilla.org))

await: await会让JavaScript引擎等到promise完成之后再返回结果

根据上面的介绍,我们可以利用promise的特性,用await一个promise的方式,中断一个函数,等到适当的时候,我们再结束掉这个promise,使中断的函数继续执行。

核心代码

class Defer {
    constructor() {                                
        this.promise = new Promise((resolve, reject) => { 
            this.resolve = resolve; 
            this.reject = reject;  
        });  
    }      
}

代码非常简单,一个类,这个类中提供一个promise的属性,这个promise的resolve和reject都是在实例化之后在和实例化出来的promise绑定的。先看一个很简单的例子

let mydefer = null;

const func1 = async function () {
  console.log(1);
  mydefer = new Defer();
  await mydefer.promise;
  console.log(2);
};
const func2 = function () {
  console.log(3);
  mydefer.resolve();
};

func1();
setTimeout(() => {
  func2();
}, 3000);

/*
结果
1
3
2
*/

结合结果,我们可以很明显看到func1执行到await这里之后被暂停,直到mydefer被resolve后,func1才继续往下执行。

业务例子

接下来我们把这个功能用到一个业务例子,使用elementui的tree组件,懒加载数据,支持全选

1.png

<div id='app'>
  <el-tree
  :props="props"
  :load="loadNode"
  lazy
  show-checkbox>
    <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button
            v-if="data.hasleaf"
            type="text"
            size="mini"
            @click.stop="handleSelect(node,data)">
            全选
          </el-button>
        </span>
      </span>
</el-tree>
</div>
.custom-tree-node {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    padding-right: 8px;
  }
class Defer {
    constructor() {                                
        this.promise = new Promise((resolve, reject) => { 
            this.resolve = resolve; 
            this.reject = reject;  
        });  
    }      
}
new Vue({
  el: '#app',
  data: {
    props: {
          label: 'name',
          children: 'zones',
          isLeaf: 'leaf'
        },
    mydefer: null
  },
  methods: {
    loadNode(node, resolve) {
        if (node.level === 0) {
          return resolve([{ name: '父节点', hasleaf: false }]);
        }
        if (node.level > 2) return resolve([]);
        if(node.level === 1) {         
          setTimeout(() => {
            const data = [{
              name: '父节点',
              leaf: true
            }, {
              name: '父节点',
              hasleaf: true
            }];

            resolve(data);
          }, 500);
        } else {
          setTimeout(() => {
            const data = [{
              name: '叶子节点',
              leaf: true
            }, {
              name: '叶子节点',
              hasleaf: false,
              leaf: true
            }];

            resolve(data);
            if(this.mydefer) {
              this.mydefer.resolve()//获取数据后,把promise给resolve掉,使全选函数继续执行
            }
          }, 500);
        }
      },
     
     //全选函数
    async handleSelect(node, data) {
      node.expand()// 手动展开节点
      if(node.childNodes.length !== 0) {
        node.childNodes.forEach(item => {
          item.checked = true
        })
      } else {
        
        this.mydefer = new Defer()
        await this.mydefer.promise
        this.mydefer = null
        node.childNodes.forEach(item => {
          item.checked = true
        })
      }
    }
  }
})

利用elementui中tree组件实现懒加载和全选功能 (codepen.io)这是在线例子

上面的代码的业务逻辑是,当一个的节点的hasleaf为true的时候,需要显示一个全选按钮,点击全选之后,把加载出来的数据的勾选框选上。

实现的方法就是点击全选按钮,手动展开节点,如果已经有子节点,则全选上,如果没有,在执行expand函数的时候,tree会触发loadNode函数的执行,所以我们利用promise暂停handleSelect的执行,等到数据获取完(业务是用接口,我这里是用setTimeOut模拟)再把promise给resolve掉,从而使得在设置checked的时候,node的childNodes已经是有数据的了。

以上就是这次的利用await和promise中断函数的介绍。