深入理解Promise.all

本文详细探讨了Promise.all的工作原理,包括其如何将多个Promise实例转换为一个Promise,以及只有所有子Promise都成功时才触发完成。此外,还解释了Promise.all中的异步执行实际上是并行的,并通过示例证明了这一点。同时,文章提出了在高并发情况下使用Promise.all可能带来的问题,并提出了解决方案,如通过限制并发数量来避免服务器压力过大。最后,介绍了如何通过自定义函数实现类似Promise.all的功能,以控制并发执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入理解Promise.all

了解es6的Promise的人应该都听过Promise.all,而且应该是大多数的人都用过Promise.all这个方法。首先Promise.all可以将多个Promise实例包装成一个Promise实例。

let p = Promise.all([p1, p2, p3])

Promise.all方法可以接受一个数组作为参数,数组中的每一项都是一个Promise的对象实例(如果不是,就会先调用Promise.reslove方法,将参数转化为Promise对象实例,再进行下一步的处理)。Promise.all方法的参数不一定是数组,但是必须具有Iterator接口。

  1. 只有数组里每个Promise的状态都编程了Fulfilled,Promise.all的状态才会变成Fulefilled,然后将每一个状态发返回的值,组装程一个数组返回给后面的回调函数。
  2. 只要数组里面有一个Promise的状态是Rejected,Promise.all的状态就会变成Rejected。这个时候第一个被Rejected的实例的结果会传递给后面你的回调。
  3. 如果作为参数的Promise实例自身定义了catch方法,那么他被Rejected时并不会被Promise.all的catch的方法给接受。

Promise.all队列中异步的执行顺序

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。但是针对于Promse.all中的多个异步的执行顺序是并行的还是串行的呢?这边我通过一个例子来证明一下:

function promise1 () {
  return new Promise(resolve => {
    console.log('promise1 start');
    setTimeout(() => {
      console.log('promise1 over');
      resolve();
    }, 100);
  })
}
function promise2 () {
  return new Promise(resolve => {
    console.log('promise2 start');
    setTimeout(() => {
      console.log('promise2 over');
      resolve();
    }, 90);
  })
}

Promise.all([promise1(), promise2()])

我们看上面的例子,如果说,Promise.all中的方法是串行的话,那么输出的顺序应该是 promise1 start -> promise1 over -> promise2 start -> promise2 over。但是实际的输出的顺序却不是这样的。具体的结果如下图:

这也就说明,在Promise.all中的执行的顺序其实应该是并行的(其实这边说并行是有问题的)。

如何实现Promise.all

在我们都知道了Promise.all的基本原理和串并行的关系之后,我们就来动手实现一下这个Promise.all的内容。

Promise.all = function (promise) {
  return new Promise((resolve, reject) => {
      let index = 0
      let result = []
      if (promise.length === 0) {
          resolve(result)
      } else {
          function processValue(i, data) {
              result[i] = data
              if (++index === promise.length) {
                  resolve(result)
              }
          }
          for (let i = 0; i < promise.length; i++) {
              Promise.resolve(promise[i]).then((data) => {
                  processValue(i, data)
              }, (err) => {
                  reject(err)
                  return
              })
          }
      }
  })
}

我们来简单的分析一下,首先我们知道,Promise.all返回的结果是一个Promise对象,那么,我们的方法也返回这样的一个对象;接着,我们上面讲到了,有关Promise.all的内部是并行执行的因此们这边不用等待上一个任务执行完再去执行,而是可以直接将任务塞到执行队列中,当每一个reslove之后,我们将最终的结果给reslove掉,如果其中有一个存在问题,我们就直接reject掉。因此,就形成了我们上面的内容。

Promise.all并发限制

现在我们清楚了Promse.all在处理多个异步处理时非常有用。但是如果说这个异步特别多的时候会是怎样的结果呢。比如说我们要加载100个接口的数据,当这个100个接口的数据加载完成之后我们在给返回一个加载完成。如果我们按照Promise.all的思路去写的话,我们会这么写:

let urls = [
  'https://www.api.com/images/getData1',
  'https://www.api.com/images/getData2',
  'https://www.api.com/images/getData3',
  'https://www.api.com/images/getData4',
  'https://www.api.com/images/getData5',
  'https://www.api.com/images/getData6',
   ……
   'https://www.api.com/images/getData100'
];
function loadData(url) {
  return new Promise((resolve, reject) => {
      $.get(url, result => {
        resolve(result)
      })
  })
};

let allList = []
urls.forEach(url => allList.push(loadData(url)))
Promise.all(allList).then(() => {
  console.log('success')
})

我们想象一下,如果我们一下给后台同时发送1000个请求,那么后台能够扛得动么,对于这样的问题我们需要怎么去解决呢,这个时候我们会想根据Promise.all的原理,将Promise.all的结果改装成串行执行的,如下:

Promise.asyncAll = function (promise) {
  return new Promise((resolve, reject) => {
      let index = 0
      let result = []
      if (promise.length === 0) {
          resolve(result)
      } else {
        function runPromise () {
          if (index === promise.length) {
            resolve(result);
          } else {
            Promise.resolve(promise[index]).then(data => {
              result[index] = data;
              ++index;
              runPromise()
            }, err => {
              reject(err)
            });
          }
        }
        runPromise()
      }
  })
}

这样就不容易出现高并发的问题,但是新的问题又来了,我们这样操作的话,其实我们的执行效率非常的低。假如说我们每个请求是1s这样的100个请求的话所用的时间就是100s,我们要等很久。那么面对这个问题我们怎么去处理呢,其实我们可以考虑一下,一次并行请求5个,这样的话,对服务器的压力相对来说会减少一些,而且等待请求完成的时间也会相对应的减少一些。这个时候我们就依赖于我们上面的Promise.all的方法来进行实现:

Promise.asyncStep = function (promises) {
  return new Promise((resolve, reject) => {
    let index = 0
    let stepCount = 5
    let result = []
    let count = promise.length
    if (promise.length === 0) {
        resolve(result)
    } else {
      function runPromise () {
        if (index === promise.length) {
          resolve(result);
        } else {
          stepCount = Math.min(count, stepCount)
          for (let i = 0; i < stepCount; i++) {
            --count;
            Promise.resolve(promise[index]).then(data => {
              result[index] = data;
              ++index;
            }, err => {
              reject(err)
            });
          }
          runPromise();
        }
      }
      runPromise()
    }
  })
}
### 关于ArcGIS License Server无法启动的解决方案 当遇到ArcGIS License Server无法启动的情况,可以从以下几个方面排查并解决问题: #### 1. **检查网络配置** 确保License Server所在的计算机能够被其他客户端正常访问。如果是在局域网环境中部署了ArcGIS Server Local,则需要确认该环境下的网络设置是否允许远程连接AO组件[^1]。 #### 2. **验证服务状态** 检查ArcGIS Server Object Manager (SOM) 的运行情况。通常情况下,在Host SOM机器上需将此服务更改为由本地系统账户登录,并重启相关服务来恢复其正常工作流程[^2]。 #### 3. **审查日志文件** 查看ArcGIS License Manager的日志记录,寻找任何可能指示错误原因的信息。这些日志可以帮助识别具体是什么阻止了许可服务器的成功初始化。 #### 4. **权限问题** 确认用于启动ArcGIS License Server的服务账号具有足够的权限执行所需操作。这包括但不限于读取/写入特定目录的权利以及与其他必要进程通信的能力。 #### 5. **软件版本兼容性** 保证所使用的ArcGIS产品及其依赖项之间存在良好的版本匹配度。不一致可能会导致意外行为完全失败激活license server的功能。 #### 示例代码片段:修改服务登录身份 以下是更改Windows服务登录凭据的一个简单PowerShell脚本例子: ```powershell $serviceName = "ArcGISServerObjectManager" $newUsername = ".\LocalSystemUser" # 替换为实际用户名 $newPassword = ConvertTo-SecureString "" -AsPlainText -Force Set-Service -Name $serviceName -StartupType Automatic New-ServiceCredential -ServiceName $serviceName -Account $newUsername -Password $newPassword Restart-Service -Name $serviceName ``` 上述脚本仅作为示范用途,请依据实际情况调整参数值后再实施。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值