Bluebird-Collections

本文深入讲解了Promise API的各种实例方法及静态方法的应用场景,包括Promise.all、Promise.props、Promise.any等,通过具体示例展示了如何利用这些方法进行异步操作管理和文件系统处理。

Promise实例方法和Promise类核心静态方法用于处理promise或者混合型(mixed)promise和值的集合。

所有的集合实例方法都等效于Promise对象中的静态方法,例如:somePromise.map(...)... 等效于 Promise.map(somePromise,…)…somePromise.all和Promise.all一样。

集合方法都不能够修改原来的输入。如果他们值定义了undefined,我们看做Holes in Array(holes in Array)。

Promise.all

Promise.all(Iterable<any>|Promise<Iterable<any>> input) –> Promise

当你等待多个Promise完成时候,这个方法非常有用。

给一个可迭代类型Iterable(数组是可迭代),或者一个可迭代的Promise可以产出Promise或者产出Promise和其他值混合体(注:Promise和其他类型值的混合体),Iterable成为数组一样遍历所有的值,而且,当所有的项(item)的Promise都为为fulfilled,Promise.all才会返回一个fulfilled的Promise。返回来的值是一个和原来数组一样的大小的值。如果有任何一个Promise为rejects在这个集合中。这个Promise集合将会返回是rejectsed。

var files = [];
for (var i = 0; i < 100; ++i) {
    files.push(fs.writeFileAsync("file-" + i + ".txt", "", "utf-8"));
}
Promise.all(files).then(function() {
    console.log("all the files were created");
});

这个方法和原生Promise中的Promise.all兼容。

Promise.props

Promise.props(Object|Map|Promise<Object|Map> input) -> Promise

和Promise.all一样,但是他的迭代值是对象类型或者Maps* entries(Map类实体),当所有的参数对象属性或者是Maps* Values 都是fulfilled条件满足Promise返回是fulfilled。Promise 的 fulfilled值是一个对象或者一个Map,fulfilled状态值各自keys对应原来对象和Map,如果任何对象中或者map中的Promise rejects了,然后整个Promise return rejected

如果Object参数是个可靠的Promise,然后它将会向对待对象作为一个Promise,而不是对象属性。所以其他的对象类型(除了Map),对待他们的属性同Object.Keys返回值-这个对象具有可列举的属性

*只有原生的ECMAScript 6 Map 实现类是被支持的。

**如果map的key值碰巧是一个Promise,他们不会去等待,并且最后结果集Map还是保持原来的Promise实例。

Promise.props({
    pictures: getPictures(),
    comments: getComments(),
    tweets: getTweets()
}).then(function(result) {
    console.log(result.tweets, result.pictures, result.comments);
});
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var _ = require("lodash");
var path = require("path");
var util = require("util");

function directorySizeInfo(root) {
    var counts = {dirs: 0, files: 0};
    var stats = (function reader(root) {
        return fs.readdirAsync(root).map(function(fileName) {
            var filePath = path.join(root, fileName);
            return fs.statAsync(filePath).then(function(stat) {
                stat.filePath = filePath;
                if (stat.isDirectory()) {
                    counts.dirs++;
                    return reader(filePath)
                }
                counts.files++;
                return stat;
            });
        }).then(_.flatten);
    })(root).then(_);

    var smallest = stats.call("min", "size").call("pick", "size", "filePath").call("value");
    var largest = stats.call("max", "size").call("pick", "size", "filePath").call("value");
    var totalSize = stats.call("pluck", "size").call("reduce", function(a, b) {
        return a + b;
    }, 0);

    return Promise.props({
        counts: counts,
        smallest: smallest,
        largest: largest,
        totalSize: totalSize
    });
}


directorySizeInfo(process.argv[2] || ".").then(function(sizeInfo) {
    console.log(util.format("                                                \n\
        %d directories, %d files                                             \n\
        Total size: %d bytes                                                 \n\
        Smallest file: %s with %d bytes                                      \n\
        Largest file: %s with %d bytes                                       \n\
    ", sizeInfo.counts.dirs, sizeInfo.counts.files, sizeInfo.totalSize,
        sizeInfo.smallest.filePath, sizeInfo.smallest.size,
        sizeInfo.largest.filePath, sizeInfo.largest.size));
});

注意:如果你不需要检索对象的属性时候,可以使用Promise.join:更方便一些

Promise.join(getPictures(), getComments(), getTweets(),
    function(pictures, comments, tweets) {
    console.log(pictures, comments, tweets);
});

Promise.any

Promise.any(Iterable<any>|Promise<Iterable<any>> input) -> Promise

Like Promise.some, with 1 as count. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly.

Promise.some

Promise.some(
    Iterable<any>|Promise<Iterable<any>> input,
    int count
) -> Promise

给一个可迭代器(数组是可迭代器),或者是promise迭代器,一个可以产生出promise(或者primise和values掺和体),遍历迭代器中所有的值像迭代数组一样,如果迭代器中fulfilled个数有大于参数count话,则这个返回fulfilled,fulfilled values作为一个数组且根据fulfille完成前后顺序。

这个例子ping 4个网站,记录最快的两个在控制台上:

Promise.some([
    ping("ns1.example.com"),
    ping("ns2.example.com"),
    ping("ns3.example.com"),
    ping("ns4.example.com")
], 2).spread(function(first, second) {
    console.log(first, second);
});

如果太多的promise被拒绝(rejected)了,这个promise集合能从不变成fulfilled,他会马上rejected,且通过AggregateError按先后抛出错误原因。

你将从Promise.AggregateError得到一个AggregateError一个引用

Promise.some(...)
    .then(...)
    .then(...)
    .catch(Promise.AggregateError, function(err) {
        err.forEach(function(e) {
            console.error(e.stack);
        });
    });

Promise.map

Promise.map(
    Iterable<any>|Promise<Iterable<any>> input,
    function(any item, int index, int length) mapper,
    [Object {concurrency: int=Infinity} options]
) -> Promise

给一个可迭代器(数组是可迭代器),或者是promise迭代器,一个可以产生出promise(或者primise和values掺和体),遍历迭代器中所有的值像迭代数组一样,且使用参数中的mapper来 map the array to another .

mapper函数将会等待返回的promise而不会fulfilled直到所有mapped promises都fulfilled,才会返回Promises。如果在数组中任何一个promise rejectd,或者在mapper函数返回的promise是rejected,这返回值将会是rejected。

每一项的mapper函数尽可能都会被调用到,换而言之,when the promise for that item's index in the input array is fulfilled.他们的结果顺序不会是随机的,这.map以为这可以‘且同时性’条件,不像.all.

A common use of Promise.map is to replace the .push+Promise.all boilerplate:

var promises = [];
for (var i = 0; i < fileNames.length; ++i) {
    promises.push(fs.readFileAsync(fileNames[i]));
}
Promise.all(promises).then(function() {
    console.log("done");
});

// Using Promise.map:
Promise.map(fileNames, function(fileName) {
    // Promise.map awaits for returned promises as well.
    return fs.readFileAsync(fileName);
}).then(function() {
    console.log("done");
});

A more involved example:

var Promise = require("bluebird");
var join = Promise.join;
var fs = Promise.promisifyAll(require("fs"));
fs.readdirAsync(".").map(function(fileName) {
    var stat = fs.statAsync(fileName);
    var contents = fs.readFileAsync(fileName).catch(function ignore() {});
    return join(stat, contents, function(stat, contents) {
        return {
            stat: stat,
            fileName: fileName,
            contents: contents
        }
    });
// The return value of .map is a promise that is fulfilled with an array of the mapped values
// That means we only get here after all the files have been statted and their contents read
// into memory. If you need to do more operations per file, they should be chained in the map
// callback for concurrency.
}).call("sort", function(a, b) {
    return a.fileName.localeCompare(b.fileName);
}).each(function(file) {
    var contentLength = file.stat.isDirectory() ? "(directory)" : file.contents.length + " bytes";
    console.log(file.fileName + " last modified " + file.stat.mtime + " " + contentLength)
});
Map Option: concurrency

你也许需要制定并发限制

...map(..., {concurrency: 3});

The concurrency limit applies to Promises returned by the mapper function and it basically limits the number of Promises created. For example, if concurrency is3 and the mapper callback has been called enough so that there are three returned Promises currently pending, no further callbacks are called until one of the pending Promises resolves. So the mapper function will be called three times and it will be called again only after at least one of the Promises resolves.

Playing with the first example with and without limits, and seeing how it affects the duration when reading 20 files:

var Promise = require("bluebird");
var join = Promise.join;
var fs = Promise.promisifyAll(require("fs"));
var concurrency = parseFloat(process.argv[2] || "Infinity");
console.time("reading files");
fs.readdirAsync(".").map(function(fileName) {
    var stat = fs.statAsync(fileName);
    var contents = fs.readFileAsync(fileName).catch(function ignore() {});
    return join(stat, contents, function(stat, contents) {
        return {
            stat: stat,
            fileName: fileName,
            contents: contents
        }
    });
// The return value of .map is a promise that is fulfilled with an array of the mapped values
// That means we only get here after all the files have been statted and their contents read
// into memory. If you need to do more operations per file, they should be chained in the map
// callback for concurrency.
}, {concurrency: concurrency}).call("sort", function(a, b) {
    return a.fileName.localeCompare(b.fileName);
}).then(function() {
    console.timeEnd("reading files");
});
$ sync && echo 3 > /proc/sys/vm/drop_caches
$ node test.js 1
reading files 35ms
$ sync && echo 3 > /proc/sys/vm/drop_caches
$ node test.js Infinity
reading files: 9ms
The order map calls the mapper function on the array elements is not specified, there is no guarantee on the order in which it'll execute the maper on the elements. For order guarantee in sequential execution - see Promise.mapSeries.

转载于:https://www.cnblogs.com/xiaopen/p/5657470.html

<!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>高级甘特图 - 资源管理与日程分析</title> <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script> <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" rel="stylesheet"> <!-- 引入Chart.js用于数据显示 --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style type="text/css"> html, body { height: 100%; padding: 0px; margin: 0px; overflow: hidden; font-family: Arial, sans-serif; } .layout { display: flex; height: 100vh; } .sidebar { width: 300px; background: #f5f5f5; border-right: 1px solid #ddd; padding: 10px; overflow-y: auto; } .main-content { flex: 1; display: flex; flex-direction: column; } .toolbar { padding: 10px; background: #f0f0f0; border-bottom: 1px solid #ddd; display: flex; gap: 10px; } .view-tabs { display: flex; background: #e0e0e0; } .tab { padding: 10px 20px; cursor: pointer; border-right: 1px solid #ccc; } .tab.active { background: #fff; font-weight: bold; } .gantt-container { flex: 1; } .analysis-panel { padding: 15px; background: #fff; border-top: 1px solid #ddd; max-height: 250px; overflow-y: auto; } .chart-container { height: 200px; margin-top: 10px; } .resource-item { padding: 8px; margin: 5px 0; background: #fff; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; } .resource-item.selected { background: #e3f2fd; border-color: #2196f3; } .resource-load { height: 20px; background: #e0e0e0; border-radius: 10px; margin-top: 5px; overflow: hidden; } .resource-load-bar { height: 100%; background: #4caf50; border-radius: 10px; } .overloaded { background: #f44336; } </style> </head> <body> <div class="layout"> <div class="sidebar"> <h3>资源管理</h3> <div id="resources-list"></div> <button onclick="showAddResourceForm()">添加资源</button> <h3>资源负载</h3> <div id="resource-load-chart" class="chart-container"></div> <h3>项目进度</h3> <div id="progress-analysis"> <div>总任务: <span id="total-tasks">0</span></div> <div>已完成: <span id="completed-tasks">0</span></div> <div>进行中: <span id="inprogress-tasks">0</span></div> <div>未开始: <span id="notstarted-tasks">0</span></div> <div>总体进度: <span id="overall-progress">0%</span></div> </div> </div> <div class="main-content"> <div class="toolbar"> <button onclick="switchView('gantt')">甘特图视图</button> <button onclick="switchView('resources')">资源视图</button> <button onclick="loadAnalysis()">刷新分析</button> <button onclick="showResourceLoad()">资源负载分析</button> </div> <div class="view-tabs"> <div class="tab active" data-view="gantt">任务视图</div> <div class="tab" data-view="resources">资源分配</div> </div> <div class="gantt-container"> <div id="gantt_here" style='width:100%; height:100%;'></div> </div> <div class="analysis-panel"> <h4>资源负载分析</h4> <div id="load-analysis-chart" class="chart-container"></div> </div> </div> </div> <!-- 添加资源的模态框 --> <div id="resource-modal" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background:white; padding:20px; border:1px solid #ccc; z-index:1000;"> <h3>添加资源</h3> <div> <label>名称: <input type="text" id="resource-name"></label> </div> <div> <label>邮箱: <input type="email" id="resource-email"></label> </div> <div> <label>角色: <input type="text" id="resource-role"></label> </div> <div> <label>容量(%): <input type="number" id="resource-capacity" min="1" max="100" value="100"></label> </div> <div> <button onclick="addResource()">保存</button> <button onclick="document.getElementById('resource-modal').style.display='none'">取消</button> </div> </div> <script type="text/javascript"> // 初始化配置 gantt.config.date_format = "%Y-%m-%d %H:%i:%s"; gantt.config.order_branch = true; gantt.config.order_branch_free = true; gantt.config.work_time = true; // 启用资源管理插件 gantt.plugins({ resource: true }); // 配置资源视图 gantt.config.resource_property = "resources"; gantt.config.resource_store = "resources"; gantt.config.show_resources = true; // 初始化甘特图 gantt.init("gantt_here"); // 配置灯光框(编辑表单)以支持资源分配 gantt.config.lightbox.sections = [ {name: "description", height: 70, map_to: "text", type: "textarea", focus: true}, {name: "time", height: 72, map_to: "auto", type: "duration"}, {name: "resources", height: 50, map_to: "resources", type: "resources", options: gantt.serverList("resources")} ]; // 加载数据 gantt.load("/data"); // 初始化数据处理器 const dp = new gantt.dataProcessor("/data"); dp.init(gantt); dp.setTransactionMode("REST"); // 视图切换 function switchView(viewType) { if (viewType === "resources") { gantt.config.show_resources = true; gantt.render(); } else { gantt.config.show_resources = false; gantt.render(); } } // 加载资源列表 function loadResources() { fetch("/data/resources") .then(response => response.json()) .then(resources => { const list = document.getElementById("resources-list"); list.innerHTML = ""; resources.forEach(resource => { const item = document.createElement("div"); item.className = "resource-item"; item.innerHTML = ` <strong>${resource.name}</strong> (${resource.role}) <div>容量: ${resource.capacity}%</div> <div class="resource-load"> <div class="resource-load-bar" style="width: ${Math.min(resource.capacity, 100)}%"></div> </div> `; item.onclick = () => showResourceDetails(resource.id); list.appendChild(item); }); }); } // 显示添加资源表单 function showAddResourceForm() { document.getElementById("resource-modal").style.display = "block"; } // 添加资源 function addResource() { const name = document.getElementById("resource-name").value; const email = document.getElementById("resource-email").value; const role = document.getElementById("resource-role").value; const capacity = document.getElementById("resource-capacity").value; fetch("/data/resource", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email, role, capacity }) }) .then(response => response.json()) .then(data => { if (data.action === "inserted") { document.getElementById("resource-modal").style.display = "none"; loadResources(); // 刷新资源列表 gantt.updateCollections(); } }); } // 加载分析数据 function loadAnalysis() { // 加载项目进度分析 fetch("/data/progress-analysis") .then(response => response.json()) .then(analysis => { document.getElementById("total-tasks").textContent = analysis.total_tasks; document.getElementById("completed-tasks").textContent = analysis.completed_tasks; document.getElementById("inprogress-tasks").textContent = analysis.in_progress_tasks; document.getElementById("notstarted-tasks").textContent = analysis.not_started_tasks; document.getElementById("overall-progress").textContent = analysis.overall_progress.toFixed(2) + "%"; }); // 加载资源负载分析 showResourceLoad(); } // 显示资源负载分析 function showResourceLoad() { const today = new Date(); const startDate = new Date(today.getFullYear(), today.getMonth(), 1); const endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0); const formatDate = date => date.toISOString().split('T')[0]; fetch(`/data/resource-load?start_date=${formatDate(startDate)}&end_date=${formatDate(endDate)}`) .then(response => response.json()) .then(data => { renderResourceLoadChart(data); }); } // 渲染资源负载图表 function renderResourceLoadChart(data) { // 这里简化处理,实际应该使用Chart.js创建详细图表 const ctx = document.getElementById('load-analysis-chart').getContext('2d'); // 按资源分组数据 const resources = {}; data.forEach(item => { if (!resources[item.name]) { resources[item.name] = []; } resources[item.name].push({ date: item.date, load: item.assigned_workload }); }); // 创建图表 new Chart(ctx, { type: 'bar', data: { labels: Object.keys(resources), datasets: [{ label: '资源负载', data: Object.values(resources).map(resource => resource.reduce((sum, day) => sum + day.load, 0) / resource.length ), backgroundColor: Object.values(resources).map(resource => { const avgLoad = resource.reduce((sum, day) => sum + day.load, 0) / resource.length; return avgLoad > 100 ? '#f44336' : '#4caf50'; }) }] }, options: { scales: { y: { beginAtZero: true, title: { display: true, text: '负载 (%)' } } } } }); } // 初始化页面 document.addEventListener("DOMContentLoaded", function() { // 加载资源列表 loadResources(); // 加载分析数据 loadAnalysis(); // 设置标签切换 document.querySelectorAll(".tab").forEach(tab => { tab.addEventListener("click", function() { document.querySelectorAll(".tab").forEach(t => t.classList.remove("active")); this.classList.add("active"); switchView(this.getAttribute("data-view")); }); }); // 设置定期刷新 setInterval(loadAnalysis, 60000); // 每分钟刷新一次分析数据 }); </script> </body> </html> const express = require('express'); const bodyParser = require('body-parser'); const path = require('path'); const Promise = require('bluebird'); require("date-format-lite"); const port = 1337; const app = express(); app.use(express.static(path.join(__dirname, "public"))); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // 添加JSON解析中间件 // 允许跨域 app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); next(); }); // 数据库配置 const mysql = require('promise-mysql'); async function serverСonfig() { const db = await mysql.createPool({ host: 'localhost', user: 'root', password: 'root123', database: 'gantt_howto_node' }); // 获取任务数据(包含资源信息) app.get("/data", async (req, res) => { try { const [tasks, links, resources, assignments] = await Promise.all([ db.query("SELECT * FROM gantt_tasks ORDER BY sortorder ASC"), db.query("SELECT * FROM gantt_links"), db.query("SELECT * FROM gantt_resources"), db.query("SELECT * FROM gantt_assignments") ]); // 处理任务数据 for (let i = 0; i < tasks.length; i++) { tasks[i].start_date = tasks[i].start_date.format("YYYY-MM-DD hh:mm:ss"); tasks[i].open = true; // 添加资源分配信息 const taskAssignments = assignments.filter(a => a.task_id == tasks[i].id); tasks[i].resources = taskAssignments.map(a => a.resource_id); } res.send({ data: tasks, collections: { links: links, resources: resources } }); } catch (error) { sendResponse(res, "error", null, error); } }); // 获取资源负载数据 app.get("/data/resource-load", async (req, res) => { try { const { start_date, end_date } = req.query; const resourceLoad = await db.query(` SELECT r.id, r.name, r.capacity, DATE(d.date) as date, COALESCE(SUM(a.units * t.duration / 8), 0) as assigned_workload FROM gantt_resources r CROSS JOIN ( SELECT DATE_ADD(?, INTERVAL seq.seq DAY) as date FROM ( SELECT (a.N + b.N * 10) seq FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b ) seq WHERE DATE_ADD(?, INTERVAL seq.seq DAY) <= ? ) d LEFT JOIN gantt_assignments a ON a.resource_id = r.id LEFT JOIN gantt_tasks t ON t.id = a.task_id AND d.date BETWEEN t.start_date AND DATE_ADD(t.start_date, INTERVAL t.duration DAY) WHERE d.date BETWEEN ? AND ? GROUP BY r.id, d.date ORDER BY r.id, d.date `, [start_date, start_date, end_date, start_date, end_date]); res.json(resourceLoad); } catch (error) { sendResponse(res, "error", null, error); } }); // 获取项目进度分析 app.get("/data/progress-analysis", async (req, res) => { try { const analysis = await db.query(` SELECT COUNT(*) as total_tasks, SUM(CASE WHEN progress = 1 THEN 1 ELSE 0 END) as completed_tasks, SUM(CASE WHEN progress > 0 AND progress < 1 THEN 1 ELSE 0 END) as in_progress_tasks, SUM(CASE WHEN progress = 0 THEN 1 ELSE 0 END) as not_started_tasks, AVG(progress) * 100 as overall_progress FROM gantt_tasks WHERE parent = 0 `); res.json(analysis[0]); } catch (error) { sendResponse(res, "error", null, error); } }); // 资源管理API app.get("/data/resources", async (req, res) => { try { const resources = await db.query("SELECT * FROM gantt_resources"); res.json(resources); } catch (error) { sendResponse(res, "error", null, error); } }); app.post("/data/resource", async (req, res) => { try { const { name, email, role, capacity } = req.body; const result = await db.query( "INSERT INTO gantt_resources (name, email, role, capacity) VALUES (?, ?, ?, ?)", [name, email, role, capacity] ); sendResponse(res, "inserted", result.insertId); } catch (error) { sendResponse(res, "error", null, error); } }); app.put("/data/resource/:id", async (req, res) => { try { const { name, email, role, capacity } = req.body; await db.query( "UPDATE gantt_resources SET name = ?, email = ?, role = ?, capacity = ? WHERE id = ?", [name, email, role, capacity, req.params.id] ); sendResponse(res, "updated"); } catch (error) { sendResponse(res, "error", null, error); } }); app.delete("/data/resource/:id", async (req, res) => { try { await db.query("DELETE FROM gantt_resources WHERE id = ?", [req.params.id]); sendResponse(res, "deleted"); } catch (error) { sendResponse(res, "error", null, error); } }); // 资源分配API app.post("/data/assignment", async (req, res) => { try { const { task_id, resource_id, units } = req.body; const result = await db.query( "INSERT INTO gantt_assignments (task_id, resource_id, units) VALUES (?, ?, ?)", [task_id, resource_id, units] ); sendResponse(res, "inserted", result.insertId); } catch (error) { sendResponse(res, "error", null, error); } }); app.delete("/data/assignment/:id", async (req, res) => { try { await db.query("DELETE FROM gantt_assignments WHERE id = ?", [req.params.id]); sendResponse(res, "deleted"); } catch (error) { sendResponse(res, "error", null, error); } }); // 保留你原有的任务和链接API... // [你原有的任务和链接API代码在这里] function sendResponse(res, action, tid, error) { if (action == "error") { console.log(error); res.status(500).send({ action: "error", message: error.message }); } else { let result = { action: action }; if (tid !== undefined && tid !== null) result.tid = tid; res.send(result); } } function getTask(data) { return { text: data.text, start_date: data.start_date.date("YYYY-MM-DD"), duration: data.duration, progress: data.progress || 0, parent: data.parent }; } function getLink(data) { return { source: data.source, target: data.target, type: data.type }; } } serverСonfig(); app.listen(port, () => { console.log("Server is running on port " + port + "..."); });这些代码有问题http://127.0.0.1:1337/data/resource-load?start_date=2025-07-31&end_date=2025-08-30 404 (Not Found) showResourceLoad @ (index):306 loadAnalysis @ (index):295 setInterval (anonymous) @ (index):378 VM1868:1 Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON VM1869:1 Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
08-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值