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

WebView2加载Node.js应用时localhost不可达,如何等待npm start完成?

解决WebView2等待npm服务启动完成后加载页面的方案

针对你遇到的WebView2因npm服务未完全启动而短暂显示"localhost不可达"的问题,以下是几种可行的解决方式:

方法1:监听npm进程输出,等待启动完成标识

React和Node服务启动时会输出特定就绪日志(比如React的Compiled successfully!、Node服务的Server running on port 5000),通过监听进程输出捕获这些标识,再触发WebView2加载页面。

修改LaunchProcessApp函数,启用输出监听

public static Process LaunchProcessApp(string WorkingDirectory, string appExe, string args, Action<string> onOutputReceived = null)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = appExe,
            RedirectStandardInput = true,
            RedirectStandardOutput = true, // 启用标准输出重定向
            RedirectStandardError = true,  // 启用错误输出重定向
            CreateNoWindow = true,
            UseShellExecute = false,                                        
        };

        if (psi.EnvironmentVariables.ContainsKey("PATH"))
        {
            if (!psi.EnvironmentVariables["PATH"].ToLower().Contains(@"\nodejs\"))
            {
                string njspath = GetNodejsPathInRegistry();
                psi.EnvironmentVariables["PATH"] += ";" + njspath+";";
            }
            if (!psi.EnvironmentVariables["PATH"].ToLower().Contains(@"\npm\"))
            {
                string appdatalocalpath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                string npmpath = appdatalocalpath + @"Roaming\npm";
                psi.EnvironmentVariables["PATH"] += npmpath + ";";
            }                  
        }
        Process p = new Process { StartInfo = psi };
        
        // 绑定输出事件
        if (onOutputReceived != null)
        {
            p.OutputDataReceived += (sender, e) =>
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    onOutputReceived(e.Data);
                }
            };
            p.ErrorDataReceived += (sender, e) =>
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    onOutputReceived(e.Data);
                }
            };
        }
        
        p.Start();
        p.BeginOutputReadLine();
        p.BeginErrorReadLine();
        p.StandardInput.WriteLine(@"cd " + WorkingDirectory);                
        p.StandardInput.WriteLine(args);
        
        return p;
    }
    catch (Exception ex)
    {
        CLogger.WriteLog(ELogLevel.ERROR, "Helpers  --> LanchReactApplication : error:" + ex);
    }
    return null;
}

修改按钮点击事件,等待服务就绪再显示主窗体

// 标记服务是否就绪
private bool isFrontendReady = false;
private bool isBackendReady = false;

// 按钮点击逻辑
bool x = checkIfReactAndNodeIsRunning(56513);
bool y = checkIfReactAndNodeIsRunning(5000);

if (!x)
{
    string frontendlocation = ThisAddIn.dataFolder + @"FrontEnd";
    ThisAddIn.clientCodeProcess = LaunchProcessApp(frontendlocation, "cmd", "npm start", (output) =>
    {
        // 检测React启动完成的标识,可根据实际日志调整
        if (output.Contains("Compiled successfully!") || output.Contains("Started development server"))
        {
            isFrontendReady = true;
            CheckIfAllServicesReady();
        }
    });
}
else
{
    isFrontendReady = true;
}

if (!y)
{
    string serverlocation = ThisAddIn.dataFolder + @"Server";
    ThisAddIn.processTSCode = LaunchProcessApp(serverlocation, "cmd", "npm start", (output) =>
    {
        // 检测Node服务启动完成的标识,可根据实际日志调整
        if (output.Contains("Server is running on port 5000"))
        {
            isBackendReady = true;
            CheckIfAllServicesReady();
        }
    });
}
else
{
    isBackendReady = true;
}

// 若服务已就绪,直接显示主窗体
CheckIfAllServicesReady();

// 检查所有服务是否就绪的方法
private void CheckIfAllServicesReady()
{
    if (isFrontendReady && isBackendReady)
    {
        // 确保在UI线程执行
        if (InvokeRequired)
        {
            Invoke(new Action(CheckIfAllServicesReady));
            return;
        }
        loaderfrm.Close();
        mainFrm.Show();
    }
}

方法2:轮询检查目标端口连通性

如果监听进程输出不可靠,可通过轮询检查端口是否能建立连接,确认服务就绪后再加载页面。

添加端口连通性检查方法

private bool IsPortReady(int port, int timeoutMs = 100)
{
    try
    {
        using (var client = new TcpClient())
        {
            var result = client.BeginConnect("localhost", port, null, null);
            var success = result.AsyncWaitHandle.WaitOne(timeoutMs);
            client.EndConnect(result);
            return success;
        }
    }
    catch
    {
        return false;
    }
}

// 异步等待端口就绪的方法
private async Task WaitForPortReady(int port, int maxAttempts = 60)
{
    int attempts = 0;
    while (attempts < maxAttempts)
    {
        if (IsPortReady(port))
            return;
        await Task.Delay(1000); // 每秒检查一次
        attempts++;
    }
    throw new TimeoutException($"Port {port} did not become ready within {maxAttempts} seconds.");
}

修改按钮点击事件,等待端口就绪

// 按钮点击逻辑
bool x = checkIfReactAndNodeIsRunning(56513);
bool y = checkIfReactAndNodeIsRunning(5000);

if (!x)
{
    string frontendlocation = ThisAddIn.dataFolder + @"FrontEnd";
    ThisAddIn.clientCodeProcess = LaunchProcessApp(frontendlocation, "cmd", "npm start");
}

if (!y)
{
    string serverlocation = ThisAddIn.dataFolder + @"Server";
    ThisAddIn.processTSCode = LaunchProcessApp(serverlocation, "cmd", "npm start");
}

// 异步等待所有端口就绪
Task.Run(async () =>
{
    try
    {
        if (!x) await WaitForPortReady(56513);
        if (!y) await WaitForPortReady(5000);
        
        // 回到UI线程显示主窗体
        Invoke(new Action(() =>
        {
            loaderfrm.Close();
            mainFrm.Show();
        }));
    }
    catch (TimeoutException ex)
    {
        Invoke(new Action(() =>
        {
            MessageBox.Show($"服务启动超时:{ex.Message}");
            loaderfrm.Close();
        }));
    }
});

方法3:在MainForm中添加加载重试机制

如果不想修改启动逻辑,可在InitBrowser方法中添加重试逻辑,页面加载失败时自动重试,直到服务就绪:

private async void InitBrowser(string args)
{
    try
    {
        webView21.Dock = DockStyle.Fill;
        var targetUri = new Uri("http://localhost:56513?mode=" + args);
        
        int retryCount = 0;
        const int maxRetries = 30;
        bool loadedSuccessfully = false;
        
        while (retryCount < maxRetries && !loadedSuccessfully)
        {
            try
            {
                webView21.Source = targetUri;
                await webView21.EnsureCoreWebView2Async();
                await webView21.CoreWebView2.NavigateAndWait(targetUri.ToString());
                loadedSuccessfully = true;
            }
            catch
            {
                retryCount++;
                await Task.Delay(1000);
            }
        }
        
        if (!loadedSuccessfully)
        {
            MessageBox.Show("页面加载超时,请检查服务是否正常启动");
        }
    }
    catch(Exception ex)
    {
        CLogger.WriteLog(ELogLevel.ERROR, "Start :: MAINFORM.cs - Function name: InitBrowser - " + ex);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 08:57:55