Created
November 15, 2022 10:12
-
-
Save jiju-MS/7575d11e1b1781133bfa9768b39ce10a to your computer and use it in GitHub Desktop.
A streaming function that continuously increments the cell value.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Streaming function | |
description: A streaming function that continuously increments the cell value. | |
host: EXCEL | |
api_set: {} | |
script: | |
content: > | |
// Copyright (c) Microsoft Corporation. | |
// Licensed under the MIT License. | |
/** | |
* Returns the arguments as an entity type | |
* @CustomFunction | |
* @param first First number | |
* @param second Second number | |
* @returns An entity containing the two numbers as properties | |
*/ | |
function entityNoStream(first: number, second: number) { | |
return _pushOperation("entityNoStream", [first, second]); | |
} | |
/** | |
* Returns the arguments as an entity type via a streaming function (with an extra delay) | |
* @CustomFunction | |
* @streaming | |
* @param first First number | |
* @param second Second number | |
* @param {CustomFunctions.StreamingInvocation<any[][]>} invocation Invocation object. | |
* @returns An entity containing the two numbers as properties | |
*/ | |
function entityAsStream(first: number, second: number, invocation: | |
CustomFunctions.StreamingInvocation<any[][]>) { | |
return _pushOperation("entityAsStream", [first, second, invocation]); | |
} | |
/** | |
* Defines the implementation of the custom functions | |
* for the function id defined in the metadata file (functions.json). | |
*/ | |
CustomFunctions.associate("entityNoStream", entityNoStream); | |
CustomFunctions.associate("entityAsStream", entityAsStream); | |
/////////////////////////////////////// | |
let _batch = []; | |
let _isBatchedRequestScheduled = false; | |
// This function encloses your custom functions as individual entries, | |
// which have some additional properties so you can keep track of whether or | |
not | |
// a request has been resolved or rejected. | |
function _pushOperation(op, args) { | |
// Create an entry for your custom function. | |
console.log("pushOperation"); | |
const invocationEntry = { | |
operation: op, // e.g. sum | |
args: args, | |
resolve: undefined, | |
reject: undefined | |
}; | |
// Create a unique promise for this invocation, | |
// and save its resolve and reject functions into the invocation entry. | |
const promise = new Promise((resolve, reject) => { | |
invocationEntry.resolve = resolve; | |
invocationEntry.reject = reject; | |
}); | |
// Push the invocation entry into the next batch. | |
_batch.push(invocationEntry); | |
// If a remote request hasn't been scheduled yet, | |
// schedule it after a certain timeout, e.g. 100 ms. | |
if (!_isBatchedRequestScheduled) { | |
console.log("schedule remote request"); | |
_isBatchedRequestScheduled = true; | |
setTimeout(_makeRemoteRequest, 100); | |
} | |
// Return the promise for this invocation. | |
return promise; | |
} | |
// This is a private helper function, used only within your custom function | |
add-in. | |
// You wouldn't call _makeRemoteRequest in Excel, for example. | |
// This function makes a request for remote processing of the whole batch, | |
// and matches the response batch to the request batch. | |
function _makeRemoteRequest() { | |
// Copy the shared batch and allow the building of a new batch while you are waiting for a response. | |
// Note the use of "splice" rather than "slice", which will modify the original _batch array | |
// to empty it out. | |
try { | |
console.log("makeRemoteRequest"); | |
const batchCopy = _batch.splice(0, _batch.length); | |
_isBatchedRequestScheduled = false; | |
// Build a simpler request batch that only contains the arguments for each invocation. | |
const requestBatch = batchCopy.map((item) => { | |
return { operation: item.operation, args: item.args }; | |
}); | |
console.log("makeRemoteRequest2"); | |
// Make the remote request. | |
_fetchFromRemoteService(requestBatch).then((responseBatch) => { | |
console.log("responseBatch in fetchFromRemoteService"); | |
// Match each value from the response batch to its corresponding invocation entry from the request batch, | |
// and resolve the invocation promise with its corresponding response value. | |
responseBatch.forEach((response, index) => { | |
if (response.error) { | |
batchCopy[index].reject(new Error(response.error)); | |
console.log("rejecting promise"); | |
} else { | |
console.log("fulfilling promise"); | |
console.log(response); | |
batchCopy[index].resolve(response.result); | |
} | |
}); | |
}); | |
console.log("makeRemoteRequest3"); | |
} catch (error) { | |
console.log("error name:" + error.name); | |
console.log("error message:" + error.message); | |
console.log(error); | |
} | |
} | |
// --------------------- A public API ------------------------------ | |
// This function simulates the work of a remote service. Because each | |
service | |
// differs, you will need to modify this function appropriately to work with | |
the service you are using. | |
// This function takes a batch of argument sets and returns a [promise of] | |
batch of values. | |
// NOTE: When implementing this function on a server, also apply an | |
appropriate authentication mechanism | |
// to ensure only the correct callers can access it. | |
async function _fetchFromRemoteService(requestBatch) { | |
// Simulate a slow network request to the server; | |
console.log("_fetchFromRemoteService"); | |
await pause(1000); | |
console.log("postpause"); | |
return requestBatch.map((request) => { | |
console.log("requestBatch server side"); | |
const { operation, args } = request; | |
try { | |
if (operation === "div2") { | |
// Divide the first argument by the second argument. | |
return { | |
result: args[0] / args[1] | |
}; | |
} else if (operation === "mul2") { | |
// Multiply the arguments for the given entry. | |
const myResult = args[0] * args[1]; | |
console.log(myResult); | |
return { | |
result: myResult | |
}; | |
} else if (operation === "entityNoStream") { | |
return { | |
result: [[createEntity(args[0], args[1])]] | |
}; | |
} else if (operation === "entityAsStream") { | |
setTimeout(() => { | |
let result = [[createEntity(args[0], args[1])]]; | |
console.log("INVOCATION SET RESULT >> ", args[2], result); | |
// execute the invocation | |
args[2].setResult(result); | |
}, 1000); | |
return { | |
result: "#PENDING!" | |
}; | |
} else { | |
return { | |
error: `Operation not supported: ${operation}` | |
}; | |
} | |
} catch (error) { | |
return { | |
error: `Operation failed: ${operation}` | |
}; | |
} | |
}); | |
} | |
function pause(ms) { | |
console.log("pause"); | |
return new Promise((resolve) => setTimeout(resolve, ms)); | |
} | |
function createEntity(arg0: any, arg1: any): Excel.EntityCellValue { | |
const entity: Excel.EntityCellValue = { | |
type: "Entity", | |
text: "Numbers", | |
properties: { | |
arg0: { | |
type: "Double", | |
basicValue: arg0 | |
}, | |
arg1: { | |
type: "Double", | |
basicValue: arg1 | |
} | |
} | |
}; | |
return entity; | |
} | |
language: typescript | |
libraries: | | |
https://appsforoffice.microsoft.com/lib/1/hosted/office.js | |
@types/office-js | |
core-js@2.4.1/client/core.min.js |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment