kintone的Promise基本写法

aki发表于:2019年10月21日 12:58:13更新于:2023年03月29日 18:04:51

曾经接触过JavaScript自定义的人士应该多多少少有听过或用过“Promise”,
对于还不太熟悉编程的人来说这个概念可能会比较难。
关于介绍Promise的文章在本网站已公布数篇。
我想有不少人看完那些内容后还是不太清楚“Promise到底该怎么写”,因此本篇重点介绍Promise的写法。

跟Promise有关的文章

以前发布的关于Promise的文章如下:

使用Promise的好处

在kintone上使用Promise有两个好处。

  1. 在添加记录等情况下,可以等处理都结束后再保存记录(称为同步处理)
    像“在保存A应用之前,获取B应用的数据,并使用其数据进行处理后保存”这样的情况,需要在保存记录之前要使用kintone API获取数据或对数据进行更改等操作的话,使用Promise可以实现同步处理。
    可支持Promise的事件请参考此处

  2. 和使用回调函数时的代码相比,使用Promise的代码要简略很多。
    虽说记录详细页面显示时等不需要同步处理,但是第2点好处一样很重要,所以只要是可支持Promise的事件,基本上更加推荐Promise,而非回调函数。

Promise的基本写法

举以下例子进行说明。

  • 例如)在保存报价应用(要进行自定义的应用)的记录时,要获取商品应用(应用ID: 1)中商品A(记录ID: 1)的金额,并将该值添加到报价应用中,之后执行保存

此处提供了应用模板,以方便大家确认代码用。关于应用模板的导入方法,请参考此处

使用Promise(1次)

  • 使用Promise进行同步处理

    (() => {
      kintone.events.on('app.record.create.submit', (event) => {
        // 从商品应用中获取数据
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp) => {
          // 从商品应用中获取的数据带入报价应用的字段中保存 
          event.record.价格.value = Number(resp.record.价格.value);
          return event;
        });
      });
    })();

    像这样 kintone.api() 省略了回调函数时,会返回Promise对象。

    kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}); // 这里生成Promise对象

    return 之后,kintone端在执行 app.record.create.submit 等时候,会先等待处理结束。请注意,如果不 return Promise对象,kintone不会等待处理结束。(反过来说,像记录详细页面等不需要等到处理结束的情况,就不用 return Promise对象了。)

    然后,Promise正常结束后,使用 then() 获取处理结果,发生错误时使用 catch() 可以对获取数据失败时进行相应的处理。

  • then和catch的使用例子:

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        // 从商品应用中获取数据(使用then的话就会在resp中保存数据)
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp) => {
          // 从商品应用中获取的数据带入到报价应用中并保存 
          event.record.价格.value = Number(resp.record.价格.value);
          return event;
        }).catch((resp) => {
          // 显示错误
          event.error = resp.message;
          return event;
        });
      });
    })();

使用Promise(多次)

接下来就是问题所在。感觉好像掌握了Promise,但碰到多个Promise时,又不知道怎么办了。下来就为大家整理了一下。

  • 例如) 在保存记录等事件发生时,获取商品应用(应用ID: 1)里的商品A, B, C(记录ID: 1, 2, 3)的金额,计算其合计,并将合计添加到报价应用(要自定义的应用)中。

在上面的例子的基础上,增加要获取的记录数量。实际中使用批量获取记录的API的话,只要调用一次API就可以了。本次为了便于说明,分三次调用。

  • 使用Promise进行同步处理(多次)

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        const record = event.record;
        record.合计.value = 0;
        // 获取商品应用的数据(记录ID: 1) 
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
    
          // 合计获取的数据并带入到报价应用中
          record.合计.value += Number(resp1.record.价格.value);
    
          // 获取商品应用的数据(记录ID: 2)
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});
    
        }).then((resp2) => {
    
          // 合计获取的数据并带入到报价应用中
          record.合计.value += Number(resp2.record.价格.value);
    
          // 获取商品应用的数据(记录ID: 3) 
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});
    
        }).then((resp3) => {
    
          // 合计获取的数据并带入到报价应用中
          record.合计.value += Number(resp3.record.价格.value);
    
          // 最后return event
          return event;
        });
      });
    })();

    Promise 可以等待 then() 的处理, then() 可以重复使用。
    可能有点难理解,重点在于要 returnPromise对象。不单单是记录ID 1 的部分,记录ID 2 和 3 也是要 returnPromise对象,在后续的 then() 内就可以使用记录ID 23 的获取结果了。

    另外,像上面这样多次执行Promise时,会从上往下依次处理。

  • 使用then进行连续处理的方法(上面代码的摘要)

      // : 前面省略
      return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});
        // ↑这里return了Promise对象,因此下一行可以使用then↓
      }).then((resp2) => {
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});
        // ↑这里return了Promise对象,因此下一行可以使用then↓
      }).then((resp3) => {
      // : 后面省略
  • 加上catch对错误进行处理
    还可以像上述那样,在最后加上 catch() ,对错误进行处理。
    在获取记录1,2,3时,不管哪个地方错误了都可以使用 catch() 对错误进行处理。

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        const record = event.record;
        record.合计.value = 0;
        // 从商品应用中获取数据(记录ID: 1) 
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
    
          // 合计获取的数据并带入到报价应用中
          record.合计.value += Number(resp1.record.价格.value);
    
          // 从商品应用中获取数据(记录ID: 2)
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});
    
        }).then((resp2) => {
    
          // 合计获取的数据并带入报价应用中
          record.合计.value += Number(resp2.record.价格.value);
    
          // 从商品应用中获取数据(记录ID: 3)
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});
    
        }).then((resp3) => {
    
          // 合计获取的数据并带入到报价应用中
          record.合计.value += Number(resp3.record.价格.value);
    
          // 最后return event
          return event;
    
        }).catch((resp) => {
    
          // 显示错误
          event.error = resp.message;
          return event;
    
        });
      });
    })();

和回调函数相比

进行如上复杂的数据获取时,Promise特别的方便。和使用回调函数获取复杂的数据相比,其效果就更加明显了。

  • 例如) 获取商品应用(应用ID: 1)的商品A, B, C(记录ID: 1, 2, 3)的金额,并显示其合计(使用回调函数的话,因为不会等待获取处理就执行Submit,所以下面使用记录详情页面显示事件)

    (() => {
      'use strict';
      kintone.events.on('app.record.detail.show', (event) => {
        // 从商品应用中获取数据(记录ID: 1)
        kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}, (resp1) => {
          // 从商品应用中获取记录(记录ID: 2) 
          kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2}, (resp2) => {
            // 从商品应用中获取数据(记录ID: 3) 
            kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3}, (resp3) => {
              // 合计获取的数据并显示
              alert(Number(resp1.record.价格.value) + Number(resp2.record.价格.value) + Number(resp3.record.价格.value));
            });
          });
        });
      });
    })();

    像这样,用回调函数的写法的话,调用多个API时,函数放入主函数中,代码可读性非常差。如加入错误处理,将会变得更加复杂。

  • 包含错误处理的范例

    (() => {
      'use strict';
      kintone.events.on('app.record.detail.show', (event) => {
        // 获取商品应用的数据(记录ID: 1)
        kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}, (resp1) => {
          // 获取商品应用的数据(记录ID: 2)
          kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2}, (resp2) => {
            // 获取商品应用的数据(记录ID: 3)
            kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3}, (resp3) => {
              // 合计获取的数据并显示
              alert(Number(resp1.record.价格.value) + Number(resp2.record.价格.value) + Number(resp3.record.价格.value));
            }, (err3) => {
              // 显示错误
              alert('error', err3.message);
            });
          }, (err2) => {
            // 显示错误
            alert('error', err2.message);
          });
        }, (err1) => {
          // 显示错误
          alert('error', err1.message);
        });
      });
    })();

    像上面那样在获取复杂数据时,代码也会变得非常复杂,因此比起回调函数,这种情况还是用Promise来的简洁。

kintone.Promise的使用方法

上面也提到了,要进行同步处理时,只要 return Promise就可以了。其实不用kintone.api() 也可以返回Promise。

基本上使用kintone.api()returnPromise对象准没错的。但是处理比较复杂的时候,像下面这样更加好懂。不妨顺便一起学习一下。

也可以参考下面链接里的例子。
什么是kintone.Promise

  • 用kintone.Promise创建Promise对象

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        // 使用new kintone.Promise()创建Promise对象
        // 参数resolve用于执行成功时的处理, reject用于执行报错处理
        return new kintone.Promise((resolve, reject) => {
          kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp) => {
            // 从商品应用中获取的数据带入到报价应用中,保存
            event.record.合计.value = Number(resp.record.价格.value) || 0;
            // 成功时不是使用return,而是使用句柄resolve返回event
            resolve(event);
          });
        });
      });
    })();

动作确认

下面把代码放到实际的应用中看看。像这样,在保存时就可以跟其他应用联动处理了。

  • 应用的概要

    • 在添加报价应用的记录时,从商品应用中获取相应商品的数据。

    • 本次为了说明Promise,故意不用lookup字段

    • 效果图
      0015db28dc42e2412c960a262fe1f70

  • 代码

    /*
     * kintone.Promise sample program
     * Copyright (c) 2019 Cybozu
     *
     * Licensed under the MIT License
    */
    (() => {
      'use strict';
    
      // 商品列表的应用ID
      const PRODUCT_APP = 80;
    
      kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
        const record = event.record;
    
        // 准备获取记录的条件
        const params1 = {app: PRODUCT_APP, id: record.商品ID_1.value};
        const params2 = {app: PRODUCT_APP, id: record.商品ID_2.value};
        const params3 = {app: PRODUCT_APP, id: record.商品ID_3.value};
    
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', params1).then((resp1) => {
          // 带入商品名称_1, 价格_1中获取的数据
          record.商品名称_1.value = resp1.record.商品名称.value;
          record.价格_1.value = Number(resp1.record.价格.value) || 0;
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', params2);
        }).then((resp2) => {
          // 带入商品名称_2, 价格_2中获取的数据
          record.商品名称_2.value = resp2.record.商品名称.value;
          record.价格_2.value = Number(resp2.record.价格.value) || 0;
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', params3);
        }).then((resp3) => {
          // 带入商品名称_3, 价格_3中获取的数据
          record.商品名称_3.value = resp3.record.商品名称.value;
          record.价格_3.value = Number(resp3.record.价格.value) || 0;
    
          // 返回数据
          return event;
        }).catch(() => {
          // 发生错误时数据初始化
          record.商品名称_1.value = '';
          record.价格_1.value = '';
          record.商品名称_2.value = '';
          record.价格_2.value = '';
          record.商品名称_3.value = '';
          record.价格_3.value = '';
          alert('无法从商品列表中获取数据。');
          return event;
        });
      });
    })();

Column1: 用async/await直观的进行同步处理

要记住Promise的概念并不是一件容易的事,最近JavaScript可以使用async/await来进行简单的同步处理。
但是IE11等部分浏览器不支持,这点需要注意。
关于async/await的详情请参考此处

  • 例如) 在记录保存时的事件发生时,获取商品应用(应用ID: 1)的商品A, B, C(记录ID: 1, 2, 3)的金额,将其合计之后添加到报价应用(要自定义的应用)中。

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', async (event) => {
        event.record.合计.value = 0;
    
        // 获取商品应用中的数据(记录ID: 1) 
        const resp1 = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1});
        // 获取商品应用中的数据(记录ID: 2) 
        const resp2 = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});
        // 获取商品应用中的数据(记录ID: 3) 
        const resp3 = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});
    
        // 合计获取的数据并带入到报价应用中
        event.record.合计.value = Number(resp1.record.价格.value) + Number(resp2.record.价格.value) + Number(resp3.record.价格.value);
        return event;
      });
    })();

Column2: 常见问题

在使用Promise时,经常会写成像下面那样。这里就介绍容易出错的地方和修改方法。

明明使用了Promise却还是陷入回调地狱的情况

  • 错误示例

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        const record = event.record;
        record.合计.value = 0;
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
          record.合计.value = Number(resp1.record.价格.value);
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2}).then((resp2) => {
            record.合计.value += Number(resp2.record.价格.value);
            return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3}).then((resp3) => {
              record.合计.value += Number(resp3.record.价格.value);
              return event;
            });
          });
        }).catch((resp) => {
          event.error = resp.message;
          return event;
        });
      });
    })();

    像上面这样,在使用多个Promise时,在 then() 中放入 then() ,导致代码嵌套太多层。
    虽然也能运行,但是代码不美观,可以像 使用Promise(多次) 中介绍的那样,不要嵌套。下面是推荐的写法,请对比看看。

  • 正确示例

    (() => {
      'use strict';
      kintone.events.on('app.record.create.submit', (event) => {
        var record = event.record;
        record.合计.value = 0;
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
          record.合计.value = Number(resp1.record.价格.value;
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});
        }).then((resp2) => {
          record.合计.value += Number(resp2.record.价格.value);
          return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});
        }).then((resp3) => {
          record.合计.value += Number(resp3.record.价格.value);
          return event;
        }).catch((resp) => {
          event.error = resp.message;
          return event;
        });
      });
    })();

好不容易使用了Promise,却没有正确使用then方法链

  • 错误示例

    (() => {
      'use strict';
      kintone.events.on('app.record.detail.show', (event) => {
        let result;
        kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then((resp1) => {
          result = Number(resp1.record.价格.value);
        });
        kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2}).then((resp2) => {
          result += Number(resp2.record.价格.value);
        });
        kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3}).then((resp3) => {
          result += Number(resp3.record.价格.value);
        });
      });
    })();

虽说上面的代码也能运行,但是会并行处理各个API的调用,如果想按照顺序执行,还是要像前面的正确示例那样,需要把之后的API调用处理放在 then() 内。

该Tips在2019年2月版的 kintone中确认过。

附件:kintone Promise的基本写法.zip • 2.78KB • 下载