Promise 版本的数组遍历

我们想要对数组中的值分别进行异步操作,但是数组对象的遍历方法只支持同步方法,如果传入异步函数后其结果往往超出预期,
所以我们就要自己来实现这些遍历方法的异步版本。

这里指的遍历方法包括:map、reduce、reduceRight、forEach、filter、some、every。
我们的需求有三种:串行,并行,当其中一个Promise完成或拒绝后退出。

这里我们以一个简单的异步函数为例:

let multi = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (num) {
        resolve(num ** 2)
      } else {
        reject(new Error('num not specified'))
      }
    }, 1000)
  })
}

map

[1, 2, 3].map(async item => multi(item))
// > [Promise, Promise, Promise]

因为map能够返回Promise数组,所以我们用Promise.all将返回的数组包起来就可以了。

await Promise.all([1, 2, 3].map(multi(item))
// > [1, 4, 9]

forEach

直接使用forEach会并行执行,如果需要串行的话就需要我们自己来实现一个新方法。

Array.prototype.forEachSync = async function (callback, thisArg) {
  for (let [index, item] of Object.entries(this)) {
    await callback(item, index, this)
}

some 和 every

some在匹配到第一个true之后就会终止遍历,every反之。

Array.prototype.someSync = async function (callback, thisArg) {
  for (let [index, item] of Object.entries(this)) {
    if (await callback(item, index, this)) return true
  }
  return false
}
await [1, 2, 3].someSync(async item => multi(item))
// > true

Reduce

Promise.all 虽然能并发多个请求,但它有一个特点:错误优先。一旦出现一个reject,那么Promise.all也会reject。但大多数情况下,如果某一个请求出现问题,我们希望他不要影响接下来的请求。

[1, 2, 3].map(async item => multi(item)).reduce((task, item) => {
  return task.then(() => item);
}, Promise.resolve())

这里执行map之后全部异步请求已经发起了,我们只需要顺序处理其返回的Promise数组。task的初始值为Promise.resolve(),它相当于一个中间变量,储存当前的 promise

控制并发数

我们实现了顺序请求,还有并发请求,但是着两种方案都是两种极端,考虑一下如果我们需要发出成百上千个请求,顺序执行时间太长,并发执行对机器压力太大,所以我们需要一种折中方案,控制并发请求。

worker函数每次取出tasks中的第一个值,然后执行异步函数,完成后递归执行。默认我们有三个worker,每个worker完成后都会从目前tasks再取出一个继续执行,直到tasks为空。

function mockAPI(result, time = 1000) {
    time = time * result;
    return new Promise((resolve, reject) => {
        setTimeout(() => {
             console.log(result, time);
            resolve(result);
        }, time);
    });
}

function loadLimit(tasks, promiseFunction, MAX_CURRENCY = 3) {
    const promises = result = [];

    function worker(tasks) {
        const task = tasks.shift();
        if (!task) return;
        return promiseFunction(task).then(res => {
            result.push(res);
            return worker(tasks);
        });
    }

    for (let i = 0; i < MAX_CURRENCY; i += 1) {
        promises.push(worker(tasks));
    }

    return result;
}

loadLimit([5, 2, 3, 1, 2, 3, 1, 2, 2, 3, 3], mockAPI)

【译】使用自签名证书在本地测试 Service Workers

Service Workers非常棒。它们为我们提供了强大的功能来拦截和处理网络请求,缓存资源,发送有用的推送通知等等。由于服务工作人员具有拦截网络请求的能力,因此他们必须通过HTTPS运行,以防止被恶意开发者滥用!

Service Workers需要通过HTTPS运行,以确保网页在网络旅程中没有被篡改。如果您在部署到实时服务器之前在本地开发和测试Service Workers代码,则可以在本地主机上无需任何证书即可轻松进行测试。

最近,我一直在开发一个同时使用Service Workers和HTTP/2的Node.js应用程序。HTTP/2与Service Workers需要HTTPS的方式相同,这意味着当我在本地工作时,我需要使用自签名证书。这些自签名证书只是用于测试的伪造证书,所以浏览器会弹出证书错误页面,点击忽略但Service Workers依然不会被注册,控制台中会输出如下错误:DOMException: Failed to register a ServiceWorker: An SSL certificate error occurred when fetching the script。这使得本地测试变得更加困难,为了确保我的开发工作流在本地和部署时都是无缝的,我需要解决它!

幸运的是,有一种方法。谷歌的Chrome有一个设置,可以让你覆盖给定的域名。我不建议始终运行此操作,但它对于在本地使用自签名证书进行测试时可能会有所帮助。

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/foo --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost:1234

上面的命令将打开Goog​​le Chrome的新实例,并将您选择的来源安全地进行安全处理。你也不会再看到任何警告错误,并且你的Service Workers将会注册。

如果你使用Windows计算机,则需要启动命令提示符并替换指向Chrome安装的路径,可能类似于以下内容:

C:\Program Files (x86)\Google\Chrome\Application\chrome.exe --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost:1234

原文:
Testing Service Workers locally with self signed certificates

JS 初始化数组

直接使用Array

  • 单个正整数参数,表示返回的新数组的长度
  Array(3)      // [undefined × 3]

这里需要注意的是只能接受正整数,而且返回的是数组长度,[undefined × 3]
[undefined, undefined, undefined]是不同的,前者只有length属性,严格来说并不能称其为数组而是类数组对象。所以它也不能用mapforEach方法来遍历。

  • 多参数时,所有参数都是返回的新数组的成员
  Array(3,1)    // [3, 1] 

使用ES6中的Array.from方法

Array.from()方法从一个类数组或可迭代对象中创建一个新的数组实例。所以我们可以把直接使用Array得到的类数组作为参数,来得到一个真正的数组。ES5中可以使用Array.apply代替。

Array.from(Array(3));  // [undefined, undefined, undefined]
Array.apply(null, Array(3));  //ES5

使用ES6中的Array.fill方法填充数据

Array(3).fill(1);

这里需要注意fill方法的第一个参数是值,如果你用fill(new Foo),那么Foo对象只会构造一次,如果你的类中有随机数的话就不能用fill,而是用map方法。

Array.from(Array(100)).map(() => new Foo())

创建每个元素的值等于它的下标的数组

利用keys方法返回的可迭代对象。

Array.from(Array(3).keys())
[...Array(3).keys()]

纯 JS 回到顶部

浏览器中控制滚动条高度的是scrollTo方法,它接受两个参数,分别是相对左上角xy轴滚动的像素。scrollTo(0, 0)就能回到顶部,但这个方法是直接跳到顶部的,所以我们还需要一个动画效果来实现scrollY到0的平滑滚动。

这里我们采用 0-Pi 上的余弦平方曲线(1-cos(x))^2,来模拟贝塞尔曲线的效果。
屏幕快照 2018-05-05 下午2.12.22.png

window.scrollY表示文档在垂直方向已滚动的像素值。所以我们通过scrollY获取滚动条的初始高度,用1/4余弦平方曲线计算每一帧的滚动条需要到达的高度。通过requestAnimationFrame在30帧后到达顶部。

下面为代码实现:

function scrollTop(duration = 30) {
    let stepCount = 0;
    const initialPosition = scrollY;
    const stepPI = Math.PI / duration;
    requestAnimationFrame(step);

    function step() {
        if (stepCount < duration) {
            requestAnimationFrame(step);
            stepCount++;
            scrollTo(0, initialPosition * 
                        (1 - 0.25 * Math.pow((1 - Math.cos(stepPI * stepCount)), 2)));
        }
    }
}

参考文章:
Animated onclick scroller in pure JS