kintone的Promise基本写法

aki发表于:2019年10月21日 12:58:13更新于:2021年04月20日 15:13:26

曾经接触过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进行同步处理

    (function() {    
        kintone.events.on("app.record.create.submit", function(event) {    
            // 从商品应用中获取数据    
            return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then(function(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的使用例子:

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

使用Promise(多次)

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

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

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

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

    (function() {    
        'use strict';    
        kintone.events.on('app.record.create.submit', function(event) {    
            var record = event.record;
             record.合计.value = 0;            
            // 获取商品应用的数据(记录ID: 1)     
            return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then(function(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(function(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(function(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(function(resp1) {    
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});    
        // ↑这里return了Promise对象,因此下一行可以使用then↓    
    }).then(function(resp2) {    
        return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 3});    
        // ↑这里return了Promise对象,因此下一行可以使用then↓    
    }).then(function(resp3) {    
        // : 后面省略
  • 加上catch对错误进行处理
    还可以像上述那样,在最后加上 catch() ,对错误进行处理。
    在获取记录1,2,3时,不管哪个地方错误了都可以使用 catch() 对错误进行处理。

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

和回调函数相比

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

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

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

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

  • 包含错误处理的范例

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

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

kintone.Promise的使用方法

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

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

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

  • 用kintone.Promise创建Promise对象

    (function() {    
        'use strict';    
        kintone.events.on('app.record.create.submit', function(event) {    
            // 使用new kintone.Promise()创建Promise对象    
            // 参数resolve用于执行成功时的处理, reject用于执行报错处理    
            return new kintone.Promise(function(resolve, reject) {    
                kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}).then(function(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    
    */    
    (function() {    
        'use strict';    
        
        // 商品列表的应用ID    
        var PRODUCT_APP = 80;
        
        kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], function(event) {    
            var record = event.record;
            
            // 准备获取记录的条件    
            var params1 = {app: PRODUCT_APP, id: record.商品ID_1.value};    
            var params2 = {app: PRODUCT_APP, id: record.商品ID_2.value};    
            var params3 = {app: PRODUCT_APP, id: record.商品ID_3.value};
            
            return kintone.api(kintone.api.url('/k/v1/record', true), 'GET', params1).then(function(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(function(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(function(resp3) {    
                // 带入从商品名称_3, 价格_3中获取的数据    
                record.商品名称_3.value = resp3.record.商品名称.value;    
                record.价格_3.value = Number(resp3.record.价格.value) || 0;
                
                // 返回数据    
                return event;    
            }).catch(function() {    
                // 发生错误时数据初始化    
                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)的金额,将其合计之后添加到报价应用(要自定义的应用)中。

    (function() {    
        'use strict';    
        kintone.events.on('app.record.create.submit', async function(event) {
            record.合计.value = 0; 
            
            // 获取商品应用中的数据(记录ID: 1)     
            var resp1 = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1});    
            // 获取商品应用中的数据(记录ID: 2)    
            var resp2 = await kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 2});    
            // 获取商品应用中的数据(记录ID: 3)    
            var 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却还是陷入回调地狱的情况

  • 错误示例

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

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

  • 正确示例

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

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

  • 错误示例

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

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

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

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

    注意:贴代码时请注意格式并使用"代码语言",与本文无关的问题请至“讨论社区”提问。