曾经接触过JavaScript自定义的人士应该多多少少有听过或用过“Promise”,
对于还不太熟悉编程的人来说这个概念可能会比较难。
关于介绍Promise的文章在本网站已公布数篇。
我想有不少人看完那些内容后还是不太清楚“Promise到底该怎么写”,因此本篇重点介绍Promise的写法。
跟Promise有关的文章
以前发布的关于Promise的文章如下:
使用Promise的好处
在kintone上使用Promise有两个好处。
在添加记录等情况下,可以等处理都结束后再保存记录(称为同步处理)
像“在保存A应用之前,获取B应用的数据,并使用其数据进行处理后保存”这样的情况,需要在保存记录之前要使用kintone API获取数据或对数据进行更改等操作的话,使用Promise可以实现同步处理。
可支持Promise的事件请参考此处。和使用回调函数时的代码相比,使用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()
可以重复使用。
可能有点难理解,重点在于要return
Promise对象。不单单是记录ID1
的部分,记录ID2
和3
也是要return
Promise对象,在后续的then()
内就可以使用记录ID2
3
的获取结果了。另外,像上面这样多次执行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()
来return
Promise对象准没错的。但是处理比较复杂的时候,像下面这样更加好懂。不妨顺便一起学习一下。
也可以参考下面链接里的例子。
什么是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字段
效果图
代码
/* * 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中确认过。