资讯专栏INFORMATION COLUMN

小程序图片合成:异步并发渲染→同步阻塞渲染

zhoutao / 1528人阅读

摘要:故事开始了,小程序图片合成真机测试时,会报错。所以只能将异步并发改为同步阻塞式渲染。

故事开始了,小程序canvas图片合成 真机测试时,会报错:getImageInfo failed 。
也就是说,我这边异步请求50张图片,每张图片都是通过getImageInfo下载到本地并且绘制到canvas画布上,但是在处理的过程中,getImageInfo会出现获取本地图片错误的情况,也就是说请求50张,最后绘制出来的可能只有45张或者40张,非常明显的数据丢失,要比数据包丢包严重N倍。

经过一系列排查后,发现可能原因有2个:
1.图片请求的域名必须是小程序经过小程序验证的(大佬们通过换域名跳转解决了这个风险)
2.小程序的请求并发数超过小程序的最大并发限制, request、uploadFile、downloadFile 的最大并发限制是 10 个

开发者经验:
"代码问题,小程序同时只能发送10个网络请求,超过后就会报错。图片信息获取也占用这10个网络请求。要把图片信息获取序列化。控制在同一个时间内段内不超10个请求,我是做了队列处理,一次只发送一个请求。完成后在走下一个"

CNODE社区经验
原生promise、async怎么实现控制并发数量
1. bluebird可以做到控制并发数量。
2.用 for 遍历,然后一次拿2个,或者多个出来 再 Promise.all 之后 再 await
3.promise.map包
const promise = pmap(arr, async item => {
       //
}, 10) // 最后是 concurrency
4. 不想用库,那就把 https://github.com/sindresorh... 对应的方法,CTRL+C 过来呗

bluebird并发控制数量...并没有接触过,但是感觉底层都是async和await。

需要去温习下async和await了

async是什么?
async函数声明定义了一个异步函数,这个函数返回一个AsyncFunction对象。

async function name([param,[param[,...param]]]){

     statements

}

函数都有return的东西,async return的是什么,它返回的是AsyncFunction对象,表示执行包含在函数中的代码的异步函数,与new一个Object或者自己写的类一样,new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)

await是什么?

await操作符被用来等待一个Promise对象。它仅仅在async函数中使用。
[rv] = await expression;

说得形象些,async许下承诺,await接收承诺。
async许下了什么,许下一个承诺函数,也就是一个Promise。
await在等待什么,它在等待一个承诺,也就是一个Promise。
function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("resolved");
    }, 2000);
  });
}
async function asyncCall() {
  console.log("calling");
  var result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: "resolved"
}
asyncCall();

 =>"calling"//瞬间打印
 =>"resolved"//两秒后打印

分下下上面代码的工作原理:

asyncCall()执行
打印"calling"
resolveAfter2Seconds() 执行,Promise对象执行中
与此同时,await做好了接收Promise对象的准备
2秒后,await接收到Promise,赋值给result
打印"resolved"

温习结束,开始尝试异步控制的办法。

网友的代码只言片语,实在看不懂,到官网Promise.map来看下。

Promise.map(
`    Iterable|Promise> input,
    function(any item, int index, int length) mapper,
    [Object {concurrency: int=Infinity} options]`
) -> Promise
`var promises = [];
for (var i = 0; i < fileNames.length; ++i) {
    promises.push(fs.readFileAsync(fileNames[i]));
}
Promise.all(promises).then(function() {
    console.log("don`e");
`});
// 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");`
});
Map Option: concurrency
You may optionally specify a concurrency limit:
...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 is 3 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.

懵逼,看完并不会用。

但是经过一番折腾,终于会用了,和Array.prototype.map有点类似的,只不过Promise.map传入的callback是一个AsyncFunction,也就是返回一个Promise的函数。

做到了并发数的控制,但是控制并发数以后图片数据还是获取不完整,10个并发,9个并发,5个并发,2个并发,都试过了,都是不行。必须改异步为同步,也就是限制每次只能请求1个,也就是并发数设置为1即可,但是用户体验上非常不好,用户只能看到一张一张图片,假设有60张,需要一张一张去请求,最后再合成一张完整的图片。

但是为了保证所有图片都能下载下来,能够正常显示所有的图片,这是目前唯一的办法。

Promise.map(photoData, function (photo) {
  return getImageInfoPromisified({
    src: photo.url
  }).then(function (res) {
    imagesArr.push(res.path);
    picx = (photo.left / 2) * ratio;
    picy = ((photo.top - 360) / 2) * ratio;
    picwidth = ((photo.width / 2) - 4) * ratio;
    picheight = ((photo.height / 2) - 4) * ratio;
    ctx.drawImage(res.path, picx, picy, picwidth, picheight);
    ctx.draw(true);

  }).catch(function () {
    console.error("get location failed")
  });
}, { concurrency: 1}).then(function () {
  wx.canvasToTempFilePath({
    canvasId: "pic",
    success: function (res) {
      console.log(res.tempFilePath)
      that.setData({
        onlineImage: res.tempFilePath,
        canvasDisplay: "none"
      })
    }
  })
});

所有这个问题最后还是使用bluebird的promise.map方法控制并发请求数实现的,也就是 { concurrency: 1}这里,但是没有达到预期小于10个并发的处理预期,因为只要是并发,都会造成数据丢失。

所以只能将异步并发改为同步阻塞式渲染。

这个问题的出现与我们图片合成功能的设计架构有很大的问题,其实最理想的情况是,服务端直接返回一张完整的大图片,前端仅需一次网络请求即可,不存在同步异步这种令人头疼的问题。

虽然最后通过异步并发渲染转换为同步阻塞渲染,获取到了全部的图片资源,但是这并不是好的解决方案,所以这篇文章的主要是讲了一个异步并发数量控制的方法,也穿插着加入了一些异步并发渲染与同步阻塞渲染对比的内容,还要就是我对图片合成这个功能设计架构上的一些想法。

That"s it !

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107208.html

相关文章

  • web前端性能优化总结

    摘要:但是还是会阻塞事件,所以会可能在触发前或后执行,但是一定会在事件前触发。当监听到该图片元素进入可视窗口时,即将自定义属性中的地址存储到属性中,达到懒加载的效果。当代码执行,线程被冻结。所以的性能让变慢。 概括 涉及到的分类 网络层面 构建层面 浏览器渲染层面 服务端层面 涉及到的功能点 资源的合并与压缩 图片编解码原理和类型选择 浏览器渲染机制 懒加载预加载 浏览器存储 缓存机制...

    evin2016 评论0 收藏0
  • WEB/H5性能优化总结

    摘要:如下图所示一重绘与回流前端性能优化最关键的就是减少页面的重绘与回流。很明显就是少了一步,这是因为把会触发回流的属性用替代,这样就使渲染的过程减少了这一步,使渲染的时间减少从而提高性能。 我们今天来说说前端图形渲染优化,因为我接下来的时间可能要开始研究webgl方面的东西,所以就在这里把之前做过的H5做一个总结,现同步发布于GERRY_BLOG,TiMiGerry-知乎,转载请保留链接。...

    stdying 评论0 收藏0
  • WEB/H5性能优化总结

    摘要:如下图所示一重绘与回流前端性能优化最关键的就是减少页面的重绘与回流。很明显就是少了一步,这是因为把会触发回流的属性用替代,这样就使渲染的过程减少了这一步,使渲染的时间减少从而提高性能。 我们今天来说说前端图形渲染优化,因为我接下来的时间可能要开始研究webgl方面的东西,所以就在这里把之前做过的H5做一个总结,现同步发布于GERRY_BLOG,TiMiGerry-知乎,转载请保留链接。...

    mingde 评论0 收藏0
  • 浏览器知识

    摘要:浏览器的渲染进程是多线程的。异步请求线程在在连接后是通过浏览器新开一个线程请求将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。 [TOC] 浏览器进程线程 区分线程和进程 **- 什么是进程** 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being exe...

    Pluser 评论0 收藏0

发表评论

0条评论

zhoutao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<