You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用返回ContentService的Google Apps Script出现返回类型不支持错误求助

问题:调用Google Apps Script返回不支持的返回类型错误

我创建了一个返回ContentService.createTextOutput("Hello")的简单Google Apps Script,将其关联到Google Cloud项目,并编写了JavaScript程序以获取返回值。可以成功执行、部署该脚本,且使用部署页面的链接时一切正常,但JavaScript程序返回错误提示:

"The script completed but the returned value is not a supported return type."

查看日志可知脚本已执行完成。我的Cloud项目已启用Drive API(尽管未使用),且创建令牌时已指定该权限范围。相关JavaScript代码如下:

example.ts

import * as google from "googleapis";
import { authorizePromise } from "./authorizePromise";
import * as dotenv from "dotenv";
dotenv.config();

main();
async function main() {
  const auth = await authorizePromise();
  console.log("abut to call");
  callAppsScript(auth);
}

/**
 * Call an Apps Script function to list the folders in the user's root
 * Drive folder.
 *
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
function callAppsScript(auth) {
  var scriptId = process.env.SCRIPT_ID;
  console.log("Script ID: ", scriptId);
  var script = new google.script_v1.Script({});

  // Make the API request. The request object is included here as 'resource'.
  console.log("running", auth);
  script.scripts.run(
    {
      auth: auth,
      scriptId: scriptId,
      requestBody: { function: "doGet", devMode: true },
    },
    function (err, resp) {
      if (err) {
        // The API encountered a problem before the script started executing.
        console.log("The API returned an error: " + err);
        return;
      }
      if (resp.error) {
        // The API executed, but the script returned an error.

        // Extract the first (and only) set of error details. The values of this
        // object are the script's 'errorMessage' and 'errorType', and an array
        // of stack trace elements.
        var error = resp.error.details[0];
        console.log("Script error message: " + error.errorMessage);
        console.log("Script error stacktrace:");

        if (error.scriptStackTraceElements) {
          // There may not be a stacktrace if the script didn't start executing.
          for (var i = 0; i < error.scriptStackTraceElements.length; i++) {
            var trace = error.scriptStackTraceElements[i];
            console.log("\t%s: %s", trace.function, trace.lineNumber);
          }
        }
      } else {
        // The structure of the result will depend upon what the Apps Script
        // function returns. Here, the function returns an Apps Script Object
        // with String keys and values, and so the result is treated as a
        // Node.js object (folderSet).
        console.log(
          "debug",
          resp,
          "response",
          Object.keys(resp),
          Object.values(resp),
          // "resp data",
          // Object.keys(resp.data),
          // "xxxxx",
          // Object.values(resp.data),
          // "xxxxx",
          // Object.keys(resp.data.response),
          // "xxxxx",
          // Object.values(resp.data.response),
          JSON.stringify(resp)
        );
        var folderSet = resp.data.response;
        if (Object.keys(folderSet).length == 0) {
          console.log("No folders returned!");
        } else {
          console.log("Folders under your root folder:");
          Object.keys(folderSet).forEach(function (id) {
            console.log("\t%s (%s)", folderSet[id], id);
          });
        }
      }
    }
  );
}

authorizePromise.ts

const fs = require("fs");
const readline = require("readline");
const { google } = require("googleapis");

// If modifying these scopes, delete token.json.
const SCOPES = ["https://www.googleapis.com/auth/drive"];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = "token.json";

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */

export async function authorizePromise() {
  const credentials = JSON.parse(
    fs.readFileSync("credentials.json").toString()
  );

  return new Promise((resolve) => {
    resolve(authorize(credentials, resolve));
  });
}
async function authorize(credentials, callback) {
  console.log("authorize", credentials);
  const { client_secret, client_id, redirect_uris } = credentials.installed;
  console.log("authorize 3", client_secret, client_id, redirect_uris);
  const oAuth2Client = new google.auth.OAuth2(
    client_id,
    client_secret,
    redirect_uris[0]
  );

  // Check if we have previously stored a token.
  let token;
  try {
    token = JSON.parse(fs.readFileSync(TOKEN_PATH).toString());
  } catch {
    token = await getAccessTokenPromise(oAuth2Client);
  }
  console.log("token", token);
  oAuth2Client.setCredentials(token);
  console.log("authorize 4");
  callback(oAuth2Client);
}

function getAccessTokenPromise(oAuth2Client) {
  return new Promise((resolve) => {
    getAccessToken(oAuth2Client, resolve);
  });
}
/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getAccessToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: "offline",
    scope: SCOPES,
  });
  console.log("Authorize this app by visiting this url:", authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question("Enter the code from that page here: ", (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error("Error retrieving access token", err);
      console.log("token", token);
      oAuth2Client.setCredentials(token);
      console.log("Going here");
      console.log("g", JSON.stringify(token));
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        console.log("Err?", token, err);
        if (err) return console.error(err);
        console.log("Token stored to", TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

原因分析

doGet函数是为HTTP请求场景设计的,返回的ContentService对象是Google Apps Script用于构建HTTP响应的专用类型。但通过Google Apps Script API直接调用函数时,不支持返回这类服务端响应对象,只能返回JSON、字符串、数字、数组等可序列化的基础数据类型。

解决方案

方案1:新增专门供API调用的函数

修改Google Apps Script代码,保留原doGet用于HTTP访问,新增一个返回基础类型的函数:

// 专门供API调用的函数,返回字符串
function getHello() {
  return "Hello";
}

// 原HTTP请求处理函数
function doGet() {
  return ContentService.createTextOutput("Hello");
}

然后修改JavaScript代码中的调用目标,改为调用getHello:

// 修改requestBody中的function字段
requestBody: { function: "getHello", devMode: true },

方案2:在doGet中兼容两种调用场景

如果不想新增函数,可以通过参数判断调用来源,返回对应类型:

function doGet(e) {
  // 通过传入的参数判断是否为API调用
  if (e && e.parameter.isApiCall) {
    return "Hello";
  }
  // 正常HTTP请求返回ContentService对象
  return ContentService.createTextOutput("Hello");
}

调用时传递参数:

requestBody: { 
  function: "doGet", 
  devMode: true,
  parameters: [{isApiCall: true}] // 传递标识参数
},

额外优化建议

  • 权限范围收紧:当前使用的https://www.googleapis.com/auth/drive权限过大,如果不需要操作Drive资源,建议替换为更窄的权限,比如https://www.googleapis.com/auth/script.run或https://www.googleapis.com/auth/script.external_request,降低权限风险。
  • 确认部署权限:开发模式下调用脚本时,需确保部署设置中允许对应用户访问(比如勾选"Allow anyone, even anonymous"或指定内部用户范围)。

内容的提问来源于stack exchange,提问作者Ethan Strominger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 18:19:47