UNPKG

@esri/arcgis-rest-request

Version:

Common methods and utilities for @esri/arcgis-rest-js packages.

427 lines 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Job = void 0; const request_js_1 = require("./request.js"); const clean_url_js_1 = require("./utils/clean-url.js"); const ArcGISJobError_js_1 = require("./utils/ArcGISJobError.js"); const job_statuses_js_1 = require("./types/job-statuses.js"); const mitt_1 = require("mitt"); const process_job_params_js_1 = require("./utils/process-job-params.js"); const DefaultJobOptions = { pollingRate: 2000, startMonitoring: false }; /** * Jobs represent long running processing tasks running on ArcGIS Services. Typically these represent complex analysis tasks such as [geoprocessing tasks](https://developers.arcgis.com/rest/services-reference/enterprise/submit-gp-job.htm), [logistics analysis such as fleet routing](https://developers.arcgis.com/rest/network/api-reference/vehicle-routing-problem-service.htm) or [spatial analysis tasks](https://developers.arcgis.com/rest/analysis/api-reference/tasks-overview.htm). * * To create a {@linkcode Job}, use the {@linkcode Job.submitJob} method which will return an instance of the {@linkcode Job} class with a unique id. * * If you have an existing job you can use {@linkcode Job.serialize} and {@linkcode Job.deserialize} to save job information as a string and recreate the job to get results later. * * ```js * import { Job, JOB_STATUSES } from "@esri/arcgis-rest-request"; * * const job = async Job.submitJob(options); * * // will automatically wait for job completion and get results when the job is finished. * job.getAllResults().then((results) => {console.log(results)}) * * // watch for all status updates * job.on("status", ({jobStatus}) => {console.log(job.status)}) * ``` * * By default event monitoring is started when you call {@linkcode Job.waitForCompletion}, {@linkcode Job.getAllResults} or, {@linkcode Job.getResult} and stops automatically when those promises complete. Use {@linkcode Job.startEventMonitoring} and {@linkcode Job.stopEventMonitoring} to manually start and stop event monitoring outside those methods. Starting monitoring with {@linkcode Job.startEventMonitoring} will not stop monitoring when {@linkcode Job.waitForCompletion}, {@linkcode Job.getAllResults} or, {@linkcode Job.getResult} complete. */ class Job { constructor(options) { /** * Function that calls the {@linkcode Job.getJobInfo} to check the job status, and emits the current job status. There are custom event emitters that * the user is able to listen based on the job status. Refer to {@linkcode JOB_STATUSES} to see the various enums of the job status. * To get results array from the job task, the job status must be {@linkcode JOB_STATUSES.Success}. * * These job statuses are based on what are returned from the job request task and have been into an enum type in {@linkcode JOB_STATUSES}. * * Reference https://developers.arcgis.com/rest/services-reference/enterprise/geoanalytics-checking-job-status.htm */ this.executePoll = async () => { let result; try { result = await this.getJobInfo(); } catch (error) { this.emitter.emit(job_statuses_js_1.JOB_STATUSES.Error, error); return; } this.emitter.emit(job_statuses_js_1.JOB_STATUSES.Status, result); this.emitter.emit(result.status, result); }; const { url, id, pollingRate, authentication } = Object.assign(Object.assign({}, DefaultJobOptions), options); // Setup internal properties this.url = url; this.id = id; this.authentication = authentication; this._pollingRate = pollingRate; this.emitter = (0, mitt_1.default)(); if (options.startMonitoring) { this.startEventMonitoring(pollingRate); } } static deserialize(serializeString, options) { const jobOptions = Object.assign(Object.assign(Object.assign({}, DefaultJobOptions), JSON.parse(serializeString)), options); return (0, request_js_1.request)(`${jobOptions.url}/jobs/${jobOptions.id}`, { authentication: jobOptions.authentication }).then(() => { return new Job(jobOptions); }); } /** * Creates a new instance of {@linkcode Job} from an existing job id. * * @param options Requires request endpoint url and id from an existing job id. * @returns An new instance of Job class with options. */ static fromExistingJob(options) { const jobOptions = Object.assign(Object.assign({}, DefaultJobOptions), options); const baseUrl = (0, clean_url_js_1.cleanUrl)(jobOptions.url.replace(/\/submitJob\/?/, "")); return (0, request_js_1.request)(`${baseUrl}/jobs/${jobOptions.id}`, { authentication: jobOptions.authentication }).then(() => { return new Job(jobOptions); }); } /** * Submits a job request that will return a new instance of {@linkcode Job}. * * @param requestOptions Requires url and params from requestOptions. * @returns An new instance of Job class with the returned job id from submitJob request and requestOptions; */ static submitJob(requestOptions) { const { url, params, authentication, pollingRate, startMonitoring } = Object.assign(Object.assign({}, DefaultJobOptions), requestOptions); const processedParams = (0, process_job_params_js_1.processJobParams)(params); const baseUrl = (0, clean_url_js_1.cleanUrl)(url.replace(/\/submitJob\/?/, "")); const submitUrl = baseUrl + "/submitJob"; return (0, request_js_1.request)(submitUrl, { params: processedParams, authentication }).then((response) => new Job({ url: baseUrl, authentication, id: response.jobId, startMonitoring, pollingRate })); } /** * Getter that appends the job id to the base url. */ get jobUrl() { return `${this.url}/jobs/${this.id}`; } /** * Returns `true` if the job is polling for status changes. */ get isMonitoring() { return !!this.setIntervalHandler; } /** * The rate at which event monitoring is occurring in milliseconds. */ get pollingRate() { return this._pollingRate; } /** * Sets a new polling rate and restart polling for status changes. */ set pollingRate(newRate) { this.stopEventMonitoring(); this.startEventMonitoring(newRate); } /** * Retrieves the status of the current job. * * @returns An object with the job id and jobStatus. */ getJobInfo() { return (0, request_js_1.request)(this.jobUrl, { authentication: this.authentication }).then((rawJobInfo) => { const info = Object.assign({ id: rawJobInfo.jobId, status: undefined }, rawJobInfo); delete info.jobId; delete info.jobStatus; switch (rawJobInfo.jobStatus) { case "esriJobCancelled": info.status = job_statuses_js_1.JOB_STATUSES.Cancelled; break; case "esriJobCancelling": info.status = job_statuses_js_1.JOB_STATUSES.Cancelling; break; case "esriJobNew": info.status = job_statuses_js_1.JOB_STATUSES.New; break; case "esriJobWaiting": info.status = job_statuses_js_1.JOB_STATUSES.Waiting; break; case "esriJobExecuting": info.status = job_statuses_js_1.JOB_STATUSES.Executing; break; case "esriJobSubmitted": info.status = job_statuses_js_1.JOB_STATUSES.Submitted; break; case "esriJobTimedOut": info.status = job_statuses_js_1.JOB_STATUSES.TimedOut; break; case "esriJobFailed": info.status = job_statuses_js_1.JOB_STATUSES.Failed; break; case "expectedFailure": info.status = job_statuses_js_1.JOB_STATUSES.Failure; break; case "esriJobSucceeded": info.status = job_statuses_js_1.JOB_STATUSES.Success; break; } return info; }); } /** * A handler that listens for an eventName and returns custom handler. * * @param eventName A string of what event to listen for. * @param handler A function of what to do when eventName was called. */ on(eventName, handler) { this.emitter.on(eventName, handler); } /** * A handler that listens for an event once and returns a custom handler. * * @param eventName A string of what event to listen for. * @param handler A function of what to do when eventName was called. */ once(eventName, handler) { const fn = (arg) => { this.emitter.off(eventName, fn); handler(arg); }; this.emitter.on(eventName, fn); handler.__arcgis_job_once_original_function__ = fn; } /** * A handler that will remove a listener after its emitted and returns a custom handler. * * @param eventName A string of what event to listen for. * @param handler A function of what to do when eventName was called. */ off(eventName, handler) { if (handler.__arcgis_job_once_original_function__) { this.emitter.off(eventName, handler.__arcgis_job_once_original_function__); return; } this.emitter.off(eventName, handler); } /** * Get the specific results of a successful job by result name. To get all results see {@linkcode Job.getAllResults}. * * If monitoring is disabled it will be enabled until the job classes resolves or rejects this promise. * * ``` * Job.submitJob(options) * .then((job) => { * return job.getResult("result_name") * }).then(result => { * console.log(result); * }).catch(e => { * if(e.name === "ArcGISJobError") { * console.log("Something went wrong while running the job", e.jobInfo); * } * }) * ``` * * Will throw a {@linkcode ArcGISJobError} if it encounters a cancelled or failure status in the job. * * @param result The name of the result that you want to retrieve. * @returns An object representing the individual result of the job. */ async getResult(result) { return this.waitForCompletion().then((jobInfo) => { return (0, request_js_1.request)(this.jobUrl + "/" + jobInfo.results[result].paramUrl, { authentication: this.authentication }); }); } /** * Formats the requestOptions to JSON format. * * @returns The `Job` as a plain JavaScript object. */ toJSON() { return { id: this.id, url: this.url, startMonitoring: this.isMonitoring, pollingRate: this.pollingRate }; } /** * Converts the `Job` to a JSON string. You can rehydrate the state of the `Job` with {@linkcode Job.deserialize}. * * @returns A JSON string representing the `Job`. */ serialize() { return JSON.stringify(this); } /** * Checks for job status and if the job status is successful it resolves the job information. Otherwise will throw a {@linkcode ArcGISJobError} if it encounters a cancelled or failure status in the job. * * ``` * Job.submitJob(options) * .then((job) => { * return job.waitForCompletion(); * }) * .then((jobInfo) => { * console.log("job finished", e.jobInfo); * }) * .catch(e => { * if(e.name === "ArcGISJobError") { * console.log("Something went wrong while running the job", e.jobInfo); * } * }) * ``` * * @returns An object with a successful job status, id, and results. */ async waitForCompletion() { const jobInfo = await this.getJobInfo(); if (jobInfo.status === job_statuses_js_1.JOB_STATUSES.Success) { return Promise.resolve(jobInfo); } //if jobStatus comes back immediately with one of the statuses if (jobInfo.status === job_statuses_js_1.JOB_STATUSES.Cancelling || jobInfo.status === job_statuses_js_1.JOB_STATUSES.Cancelled || jobInfo.status === job_statuses_js_1.JOB_STATUSES.Failed || jobInfo.status === job_statuses_js_1.JOB_STATUSES.Failure || jobInfo.status === job_statuses_js_1.JOB_STATUSES.TimedOut) { this.stopInternalEventMonitoring(); return Promise.reject(new ArcGISJobError_js_1.ArcGISJobError("Job cancelled or failed.", jobInfo)); } //waits to see what the status is if not immediate return new Promise((resolve, reject) => { this.startInternalEventMonitoring(); this.once(job_statuses_js_1.JOB_STATUSES.Cancelled, (jobInfo) => { this.stopInternalEventMonitoring(); reject(new ArcGISJobError_js_1.ArcGISJobError("Job cancelled.", jobInfo)); }); this.once(job_statuses_js_1.JOB_STATUSES.TimedOut, (jobInfo) => { this.stopInternalEventMonitoring(); reject(new ArcGISJobError_js_1.ArcGISJobError("Job timed out.", jobInfo)); }); this.once(job_statuses_js_1.JOB_STATUSES.Failed, (jobInfo) => { this.stopInternalEventMonitoring(); reject(new ArcGISJobError_js_1.ArcGISJobError("Job failed.", jobInfo)); }); this.once(job_statuses_js_1.JOB_STATUSES.Success, (jobInfo) => { this.stopInternalEventMonitoring(); resolve(jobInfo); }); }); } /** * Gets all the results from a successful job by ordering all the result paramUrl requests and calling each of them until all of them are complete and returns an object with all the results. * * If monitoring is disabled it will be enabled until the job classes resolves or rejects this promise. * * ``` * Job.submitJob(options) * .then((job) => { * return job.getAllResults(); * }).then(allResults => { * console.log(allResults); * }).catch(e => { * if(e.name === "ArcGISJobError") { * console.log("Something went wrong while running the job", e.jobInfo); * } * }) * ``` * * Will throw a {@linkcode ArcGISJobError} if it encounters a cancelled or failure status in the job. * * @returns An object representing all the results from a job. */ async getAllResults() { return this.waitForCompletion().then((jobInfo) => { const keys = Object.keys(jobInfo.results); const requests = keys.map((key) => { return (0, request_js_1.request)(this.jobUrl + "/" + jobInfo.results[key].paramUrl, { authentication: this.authentication }).then((results) => { return results; }); }); return Promise.all(requests).then((resultsArray) => { return keys.reduce((finalResults, key, index) => { finalResults[keys[index]] = resultsArray[index]; return finalResults; }, {}); }); }); } /** * Cancels the job request and voids the job. * * @returns An object that has job id, job status and messages array sequencing the status of the cancellation being submitted and completed. */ cancelJob() { return (0, request_js_1.request)(this.jobUrl + "/cancel", { authentication: this.authentication, params: { id: this.id, returnMessages: false } }).then((response) => { this.emitter.emit("cancelled", response); return response; }); } /** * An internal monitoring if the user specifies startMonitoring: false, we need to check the status to see when the results are returned. */ startInternalEventMonitoring(pollingRate = DefaultJobOptions.pollingRate) { this._pollingRate = pollingRate; /* istanbul ignore else - if monitoring is already running do nothing */ if (!this.isMonitoring) { this.setIntervalHandler = setInterval(this.executePoll, this.pollingRate); } } /** * Stops the internal monitoring once the job has been successfully completed with results. */ stopInternalEventMonitoring() { if (this.isMonitoring && !this.didUserEnableMonitoring) { clearTimeout(this.setIntervalHandler); } } /** * Starts the event polling if the user enables the startMonitoring param. * * @param pollingRate Able to pass in a specific number or will default to 5000. */ startEventMonitoring(pollingRate = DefaultJobOptions.pollingRate) { this._pollingRate = pollingRate; this.didUserEnableMonitoring = true; /* istanbul ignore else - if not monitoring do nothing */ if (!this.isMonitoring) { this.setIntervalHandler = setInterval(this.executePoll, this.pollingRate); } } /** * Stops the event polling rate. This is can only be enabled if the user calls this method directly. */ stopEventMonitoring() { /* istanbul ignore else - if not monitoring do nothing */ if (this.isMonitoring && this.didUserEnableMonitoring) { clearTimeout(this.setIntervalHandler); } } } exports.Job = Job; //# sourceMappingURL=job.js.map