# 🌵 ES6

# 001: var、 let 及 const 区别?

(1) var 在全局作用域下声明的变量会挂载在 window 对象上,let 和 const 则不会。

const constValue = 1;
let letValue = 2;
var varValue = 3;

console.log(window.constValue, window.letValue, window.varValue);
// undefined undefined 3

(2)var 声明的变量会被提升到作用域的顶部,而 let 和 const 则不会。

因为存在暂时性死区(Temporal Dead Zone,简写为 TDZ)和块级作用域,所以不能在声明前就使用 let 和 const 使用声明的变量,let 和 const 只在声明所在块级作用域内有效。

暂时性死区:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

const condition = false;
if (condition) {
    var foo = "foo";
    const baz = "baz";
    let bom = "bom";
}

console.log(foo); // undefined
console.log(bar); // Uncaught ReferenceError: bar is not defined
console.log(bom); // Uncaught ReferenceError: bom is not defined

(3)var 声明的变量名字可重复,类型和值可修改;let 和 const 不能重复声明,let 声明的变量值和类型都可以改变;
const 在声明的时候必须赋予初始值,一旦声明值就不能改变(值是原始数据类型的不可改变,值是引用类型绑定的地址,地址不可变,但属性值能改变)。

var value = 1;
let value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared
const value = 3; // Uncaught SyntaxError: Identifier 'value' has already been declared


const obj;  // Uncaught SyntaxError: Missing initializer in const declaration

const obj = {name: 'obj'};
const obj = {age: 20};   //Uncaught SyntaxError: Identifier 'obj' has already been declared

obj.name = 'chieminchan';
console.log(obj);  // {name: "chieminchan"}

⚠️ 循环中的块级作用域问题:

  • 一个老生常谈的 for 循环打印 i 的问题:

    for (var i = 1; i <= 5; i++) {
        setTimeout(function timer() {
            console.log(i);
        }, 0);
    }
    

    ES6 中的 let 提供了解决这个问题的方法,let 不提升不能重复声明,也不能绑定全局作用域等等特性,可是为什么在循环中可以利用 let 多次进行对 i 声明,并且这里就能正确打印出 i 值呢?

    console.log("-----iterator with let-------");
    
    for (let i = 0; i < 3; i++) {
         let i = "abc";
        console.log(i);
    }
    
    // -----iterator with let-------
    // abc
    // abc
    // abc
    
    console.log("-----iterator with var-------");
    
    for (var i = 0; i < 3; i++) {
        var i = "abc";
        console.log(i);
    }
    
    // -----iterator with var-------
    // abc
    

    输出结果不一样的主要原因是因为,在 for 循环中使用 let 和 var,底层会使用不同的处理方式。

    当在for (let i = 0; i < 3; i++)中,即圆括号之内建立一个隐藏的作用域,然后在每次迭代循环时都创建一个新变量,并且会以之前迭代中同名变量的值将其初始化。这样子,老生常谈使用 let 打印 i 的循环过程便可以理解为:

    // 伪代码
    (let i = 0) {
        setTimeout(function timer() {
                console.log(i);
        }, 0);
    }
    
    (let i = 1) {
        setTimeout(function timer() {
            console.log(i);
        }, 0);
    }
    
    (let i = 2) {
        setTimeout(function timer() {
            console.log(i);
        }, 0);
    };
    
  • 若利用 const 代替 let 来进行 for 遍历却会产生这样子的情况:

for (const i = 0; i < 3; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, 0);
}

// Uncaught TypeError: Assignment to constant variable.

结果会报错,,因为虽然我们每次都创建了一个新的变量,但在圆括号之内建立的隐藏的作用域中尝试修改了 const 的值,所以最终会报错。

# 002: 箭头函数和普通函数的区别?

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用 new 去调用,箭头函数没有原型属性;
  2. 箭头函数不绑定 arguments,取而代之可以用扩展运算符...解决
  3. 箭头函数没有 this,函数里的 this 会根据词法作用域决定,继承外层函数调用的 this 绑定,不能通过 bind、call、apply 显式改变 this 指向。

# 003: Set 和 WeakSet、Map 和 WeakMap 区别?

Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。 Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。

Set:
类似于数组,但成员是唯一且无序的,没有重复的值。
Set 内部判断两个值是否不同,使用的算法叫做Same-value-zero equality,它类似于精确相等运算符(===),主要的区别是NaN 等于自身,而精确相等运算符认为 NaN 不等于自身。

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set; // Set {NaN}

let set1 = new Set();
set1.add(5);
set1.add("5");
console.log([...set1]); // [5, "5"]

属性:size、constructor
方法:add、delete、has、

Map:类似于对象,但键类型不限制于字符串,可多种类型。

# Map 和 Object 的对比

JS 项目中究竟应该使用 Object 还是 Map? (opens new window)

# 004: Class 和普通构造函数的区别是什么?

  1. Class 必须通过 new 调用执行,而普通构造函数不用 new 也可以执行。
  2. Class 不存在变量提升,内部采用严格模式,不可对类名进行改写。
  3. Class 中的所有方法不可枚举。Class 类无法遍历它实例原型链上的属性和方法。
function Foo(color) {
    this.color = color;
}
Foo.prototype.like = function() {
    console.log(`like${this.color}`);
};
let foo = new Foo();

for (let key in foo) {
    // color, 原型上的like也被打印出来了
}

//es6
class Foo {
    constructor(color) {
        this.color = color;
    }
    like() {
        console.log(`like${this.color}`);
    }
}
let foo = new Foo("red");

for (let key in foo) {
    // 只打印一个color,没有打印原型链上的like
    console.log(key); // color
}
  1. Class 的继承有两条继承链
    一条是:子类的proto指向父类;
    另一条是:子类的 prototype 属性的proto指向父类的 prototype 属性(es5 原型链继承,Child.prototype = new Parent()

    es6 子类通过proto属性找到父类, es5 子类通过proto找到 Function.prototype

// es5
function Father() {}
function Child() {}
Child.prototype = new Father();
Child.prototype.constructor = Child;
console.log(Child.prototype.__proto__ === Father.prototype); // true
console.log(Child.__proto__ === Function.prototype); // true

// es6
class Father {}
class Child extends Father {}
console.log(Child.__proto__ === Father); // true
  1. es5 与 es6 子类 this 的生成顺序不同(new 实例机制不一样)

es5 继承是先建立子类实例对象 this,再调用父类构造函数修饰子类实例;

es6 继承是先建立父类实例对象 this,再调用子类构造函数修饰 this。即在子类构造函数中先调用 super()方法,之后再能使用 this。

// es5 寄生组合继承,伪代码
Parente.call(Child, xxx);
Child.prototyype = Father.prototype;
Child.prototype.constructor = Child;

// es6
// es5
function Father(name, age) {
    this.name = name;
    this.age = age;
}
function Child(name, age, sex) {
    Father.call(this, name, age);
    this.sex = sex;
}
// Child.prototyype = new Father();
Object.setPrototypeof(Child.prototype, Father.prototype);
var son = new Child("小明", 12, "男");
console.log(son); // {name: '小明', age: 12, sex: '男'}

// es6
class Father {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
class Child extends Father {
    constructor(name, age, sex) {
        // 在子类构造函数中先调用 super()方法,之后再能使用 this
        // 是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例;

        // super虽然代表了父类Father的构造函数,但是返回的是子类Son的实例,即super内部的this指向的是Son,
        // 因此super()在这里相当于 Father.constructor.call(this);
        super(name, age);
        this.sex = sex;
    }
}
let son = new Child("小红", 12, "女");
console.log(son); // {name: '小红', age: 12, sex: '女'}

# 005: Class 中的 this 指向

  1. new 调用后的返回,会根据 constructor 构造器里面返回来决定(没返回则返回一个正常实例对象。有则按 constructor 返回的来)。

  2. Class 中的 this 指向:

1)类的普通方法内部如果含有 this,它默认指向类的实例。

2)如果被单独拎出来,方法中的 this 会指向该方法运行时所在的环境。(想要 this 指向实例,可以在 constructor 中使用 this,或者使用箭头函数,或者 proxy)

3)如果静态方法包含 this 关键字,这个 this 指的是类,而不是实例。(如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。)

  1. 静态方法可被子类继承,不可被实例继承,静态方法也是可以从 super 对象上调用的。

  2. 实例属性除了定义在 constructor()方法里面的 this 上面,也可以定义在类的最顶层。

  3. 静态属性指的是 Class 本身的属性,即通过 Class.propName 的方式定义或者在 Class 里面通过 static 去修饰那个属性

  4. Class 的 constructor 里面的 this 指向的是创建的实例对象,方法里的 this 指向方法的调用者

# 006: Class 中的 super 用法

super 关键字,一般表示父类的构造函数,用来新建父类的 this 对象。

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。因为子类自己的 this 对象必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。

在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则就得不到 this 对象,会报错。

# 作为函数调用

作为函数调用时,super()只能用在子类的构造函数之中,用在其他地方就会报错

super() 相当于父类.prototype.constructor.call(this),this 指向各个子类环境。

// constructor 和 super 都默认被创建
class Point {
    constructor(...args) {
        // new.target.name指向当前执行环境
        console.log(new.target.name, ...args);
    }
}

// 等同于
class ColorPoint extends Point {
    constructor(...args) {
        // Point.prototype.constructor.call(this, ...args)
        super(...args);
    }
}

new Point(); // Point
new ColorPoint(12, 13); // ColorPoint, 12, 13

# 作为对象调用

  • super 作为对象时,有以下两种使用情况:
  1. 在普通方法中,指向父类的原型对象(super.p() = 父类.prototype.p());

  2. 在静态方法中,指向父类。
    (在子类普通方法中通过 super 调用父类的方法时,父类中方法内部的 this 指向当前的子类实例。)

class Parent {
    static myMethod(msg) {
        console.log("static", msg);
    }

    myMethod(msg) {
        console.log("instance", msg);
    }
}

class Child extends Parent {
    constructor() {
        super();
        super.myMethod(msg); // Parent.prototype.myMethod(msg);
    }

    static myMethod(msg) {
        super.myMethod(msg); // Parent.myMethod(msg);
    }

    myMethod(msg) {
        super.myMethod(msg); // Parent.prototype.myMethod(msg);
    }
}

// 调用子类中的静态方法,即调用了父类中的静态方法
Child.myMethod(1); // static 1

// 普通方法被继承,调用了子类原型上的普通方法,从而调用到了父类原型对象上的方法
var child = new Child(); // instance 2
child.myMethod(2); // instance 2
  • 在子类普通方法中通过 super 调用父类的方法时,父类中方法内部的 this 指向当前的子类实例。

    由于 this 指向子类实例,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。

class A {
    constructor() {
        this.x = "a";
    }

    // A类原型上的方法中的this会指向子类的实例
    print() {
        console.log(this.x);
    }
}

class B extends A {
    constructor() {
        super();
        this.x = "b";
    }
    m() {
        // super.print()虽然调用的是A.prototype.print()
        // 但是A.prototype.print()内部的this指向子类B的实例
        super.print();
    }
}

let b = new B();
b.m(); // 'b'
class A {
    constructor() {
        this.x = 1;
    }

    // 3. 父类的静态方法print中的this指向这个类,在B中调用因此指向B类
    static print() {
        console.log(this.x);
    }
}

class B extends A {
    constructor() {
        super();
        this.x = 2;
    }

    // 2. 直接访问B中的静态方法,此时super指向父类,即调用了父类中的静态方法prinit
    static m() {
        super.print();
    }
}

// 1. 直接修改B对象上的x值为3
B.x = 3;
B.m(); // 3

# 007: ES6 的 Class 继承和 ES5 的继承区别

区别 1:
ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。

ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this。

// constructor 和 super 都默认被创建
class ColorPoint extends Point {}

// 等同于
class ColorPoint extends Point {
    constructor(...args) {
        // Point.prototype.constructor.call(this, ...args);
        // super指向父类,但super()中的this指向ColorPoint
        super(...args);
    }
}

区别 2:
ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的 prototype 属性。

Class 作为构造函数的语法糖,同时有 prototype 属性和__proto__属性,因此同时存在两条继承链:

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类 prototype 属性的__proto__属性,表示方法的继承,总是指向父类的 prototype 属性。

class A {}

class B extends A {}

B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true

之所以会有两条链,因为类的继承实现模式如下:

class A {}

class B {}

// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

const b = new B();

// 其中Object.setPrototypeOf过程
Object.setPrototypeOf = function(obj, proto) {
    obj.__proto__ = proto;
    return obj;
};

// 所以上面式子又等价于
B.prototype.__proto__ = A.prototype;
B.__proto__ = A;

# 008: Symbol 用法和应用场景

# 009: Promise.all 的用法、错误处理、以及手写原理实现

# Promise.all 用法

Pomise.all 接受一个由 promise 异步任务组成的数组,并支持同时处理多个 promise 异步任务。如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象。

当且仅当所有异步任务都执行完成被 resolve 以后,Promise.all 才会被 resolve,并返回所有异步任务结果组成的数组;如果其中有某个异步任务失败了,则会返回失败的信息,即使其他 promise 异步任务执行成功,也会返回失败。

// 被resolve
const p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function(results) {
    console.log(results); // [1, 2, 3]
});

// 被reject
const p1 = Promise.resolve(1),
    p2 = Promise.reject(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
    .then(function(results) {
        //then方法不会被执行
        console.log(results);
    })
    .catch(function(e) {
        //catch方法将会被执行,输出结果为:2
        console.log(2);
    });

# Promise.all 错误处理

有时候,我们使用 Promise.all()执行多个网络请求,可能有一个请求出错,但我们并不希望其他的网络请求也返回 reject,要错都错,这样显然是不合理的。

如何做才能做到 promise.all 中即使一个 promise 程序 reject,promise.all 依然能把其他数据正确返回呢?

方案一:在处理单个异步任务时,通过try..catch对异常情况也进行捕获,异常原因也作为 resolve 结果。

const p1 = new Promise((resolve) => {
    try {
        setTimeout(() => {
            resolve(1);
        }, 1000);
    } catch (error) {
        resolve(error);
    }
});

const p2 = new Promise((resolve, reject) => {
    try {
        console.log(xxx.bb);
    } catch (error) {
        resolve(error);
    }
});

const p3 = new Promise((resolve) => {
    try {
        setTimeout(() => {
            resolve(3);
        }, 2000);
    } catch (error) {
        resolve(error);
    }
});

Promise.all([p1, p2, p3])
    .then((res) => {
        console.log(res); // [ 1, ReferenceError: xxx is not defined at file, 3]
    })
    .catch((error) => {
        console.log("err", error);
    });

方案二:通过 promise 的 catch 捕获,并且 return 错误信息

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
}).catch((error) => error);

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("error");
    }, 1000);
}).catch((error) => error);

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(3);
    }, 1000);
}).catch((error) => error);

Promise.all([p1, p2, p3])
    .then((res) => {
        console.log(res); //  [1, "error", 3]
    })
    .catch((error) => {
        console.log("err", error);
    });

# Promise.all 手写原理实现

function PromiseAll(promises) {
    // promises也不一定为数组,但必须为可迭代对象
    if (!Array.isArray(promises)) {
        return reject("argument must be an array");
    }

    const len = promises.length;
    const result = new Array(len);
    let count = 0;

    return new Promise((resolve, reject) => {
        // 也可以通过for..of遍历
        for (let i = 0; i < len; i++) {
            Promise.resolve(promises[i])
                .then((res) => {
                    result[i] = res;
                    count++;

                    if (count === len) {
                        resolve(result);
                    }
                })
                .catch((error) => {
                    reject(error);
                });
        }
    });
}

// 测试例子
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
}).catch((error) => error);

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("error");
    }, 1000);
}).catch((error) => error);

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(3);
    }, 1000);
}).catch((error) => error);

PromiseAll([p1, p2, p3])
    .then((res) => {
        console.log(res); //  [1, "error", 3]
    })
    .catch((error) => {
        console.log("err", error);
    });

# 010: Promise.allSettled 的用法以及手写原理实现

Promise.allSettled 用法类似于 Promise.all,接受一个由 promise 异步任务组成的数组,并支持同时处理多个 promise 异步任务。

和 all 的区别就是,不管其中的 promise 元素失败还是成功,都会被 resolve,触发 then 的第一个函数,结果由包含每个 promise 的 status 和 result/reason 对象组成。

用法:

const promise1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject("promise1");
    }, 2000);
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve("promise2");
    }, 3000);
});

const promise3 = Promise.resolve("promise3");

const promise4 = Promise.reject("promise4");

Promise.allSettled([promise1, promise2, promise3, promise4]).then((args) => {
    console.log(args);
});
/*
  3000ms后输出:

  result: 
  [
	{status: "rejected", reason: "promise1"},
	{status: "fulfilled", value: "promise2"},
	{status: "fulfilled", value: "promise3"},
	{status: "rejected", reason: "promise4"}
  ]*/

手写实现:

function PromiseAllSettled(promises) {
    const len = promises.length;
    const result = new Array(len);
    let count = 0;

    return new Promise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
            Promise.resolve(promises[i])
                .then((res) => {
                    const resultItem = {
                        status: "fulfilled",
                        result: res,
                    };

                    result[i] = resultItem;
                    count++;

                    if (count === len) {
                        resolve(result);
                    }
                })
                .catch((reason) => {
                    const resultItem = {
                        status: "rejected",
                        reason,
                    };

                    result[i] = resultItem;
                    count++;

                    if (count === len) {
                        resolve(result);
                    }
                });
        }
    });
}

// 测试例子
const promise1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject("promise1");
    }, 2000);
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve("promise2");
    }, 3000);
});

const promise3 = Promise.resolve("promise3");

const promise4 = Promise.reject("promise4");

PromiseAllSettled([promise1, promise2, promise3, promise4]).then(function(
    args
) {
    console.log(args);
});
/*
  result: 
  [
	{status: "rejected", reason: "promise1"},
	{status: "fulfilled", value: "promise2"},
	{status: "fulfilled", value: "promise3"},
	{status: "rejected", reason: "promise4"}
  ]*/

# 011: Promise 串行和并行的实现

# 串行方案

// 测试用例
// 当上一个 Promise 开始执行,当其执行完毕后再调用下一个 Promise,并作为一个新 Promise 返回,下次迭代就会继续这个循环。
const createPromise = (time, id) => () =>
    new Promise((resolve) =>
        setTimeout(() => {
            console.log("promise", id);
            resolve();
        }, time)
    );

runPromises([
    createPromise(3000, 1),
    createPromise(2000, 2),
    createPromise(1000, 3),
]);

// 输出顺序
// promise 1
// promise 2
// promise 3
// Array.prototype.reduce + promise.then
const runPromises = (promiseArr) => {
    promiseArr.reduce((prevPromise, curPromise) => {
        return prevPromise.then(() => curPromise());
    }, Promise.resolve());
};

// for+async/await版本
const runPromises = async (promiseArr) => {
    for (let promise of promiseArr) {
        await promise();
    }
};

# 并行方案

// 测试用例
const createPromise = (time, id) => () =>
    new Promise((resolve) =>
        setTimeout(() => {
            console.log("promise", id);
            resolve();
        }, time)
    );

runPromises([
    createPromise(3000, 1),
    createPromise(2000, 2),
    createPromise(1000, 3),
]);

// 输出顺序
// promise 3
// promise 2
// promise 1
// 模拟promise.allSettled实现
const runPromises = (promiseArr) => {
    return new Promise((resolve, reject) => {
        const len = promiseArr.length;
        const result = new Array(len);
        let count = 0;

        for (let i = 0; i < len; i++) {
            Promise.resolve(promiseArr[i]()).then(
                (res) => {
                    result[i] = { result: res, status: "fulfilled" };
                    count++;

                    if (count === len) return result;
                },
                (error) => {
                    result[i] = { reason: res, status: "rejected" };
                    count++;

                    if (count === len) return result;
                }
            );
        }
    });
};

# 012: Promise 限制并行调度器

题目:现有 8 个图片资源的 url,已经存储在数组 urls 中,且已有一个函数 function loading,输入一个 url 链接,返回一个 Promise。该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。 要求:任何时刻同时下载的链接数量不可以超过 3 个。 请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

var urls = [
    "https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg",
    "https://www.kkkk1000.com/images/getImgData/gray.gif",
    "https://www.kkkk1000.com/images/getImgData/Particle.gif",
    "https://www.kkkk1000.com/images/getImgData/arithmetic.png",
    "https://www.kkkk1000.com/images/getImgData/arithmetic2.gif",
    "https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg",
    "https://www.kkkk1000.com/images/getImgData/arithmetic.gif",
    "https://www.kkkk1000.com/images/wxQrCode2.png",
];

function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => {
            console.log("一张图片加载完成");
            resolve();
        };
        img.onerror = reject;
        img.src = url;
    });
}
// 题目的意思是需要先并发请求3张图片,当一张图片加载完成后,又会继续发起一张图片的请求,让并发数保持在3个,直到需要加载的图片都全部发起请求。
function fetchImgWithLimit(urls = [], limit = 3, handlerFn = loadImg) {
    const allPromises = []; // 用来存储所有的Promise任务
    const executing = new Array();

    const run = () => {
        // 边界处理
        if (!urls.length) {
            return Promise.resolve();
        }

        // 处理一个 移除一个
        const url = urls.shift();

        // 被作为一个任务存储
        const promiseItem = Promise.resolve()
            .then(() => handlerFn(url))
            .then(() => {
                // 请求图片完毕后从executing队列中移除
                executing.splice(executing.indexOf(promiseItem), 1);
            });

        allPromises.push(promiseItem);
        executing.push(promiseItem);

        let cur =
            executing.length >= limit
                ? Promise.race(executing)
                : Promise.resolve();

        return cur.then(() => run());
    };

    return run().then(() => Promise.all(allPromises));
}

fetchImgWithLimit(urls, 3, loadImg)
    .then((res) => {
        console.log("all loaded success", res);
    })
    .catch((error) => {
        console.log("loaded error", error);
    });

# 013: Promise.all 捕获错误

const getData = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const ok = Math.random() > 0.5; // 模拟成功或失败
            if (ok) resolve("get data");
            else {
                reject("error"); // 正常的reject
            }
        }, 2000);
    });
};

// 方法1
const promise1 = getData().catch((err) => err);
const promise2 = getData().catch((err) => err);

Promise.all([promise1, promise2])
    .then((response) => {
        // 业务逻辑...
    })
    .catch((response) => {
        console.log(response);
    });

// 方法2-方法1的封装
const getPromiseResult = (arr) => {
    const promiseArr = arr.map((promise) => promise.catch((err) => err));
    return Promise.all(promiseArr)
        .then((response) => {
            // 业务逻辑...
        })
        .catch((response) => {
            console.log(response);
        });
};

// allSettled
Promise.allSettled([promise1, promise2, promise3, promise4]).then(function(
    args
) {
    console.log(args);
    /*
  result: 
  [
    {"status":"rejected","reason":"promise1"}, 
    {"status":"fulfilled","value":"promise2"},
    {"status":"fulfilled","value":"promise3"}, 
    {"status":"rejected","reason":"promise4"}
  ]*/
});

# 014: async/await 捕获错误

function getData(data) {
    return new Promise((resolve, reject) => {
        if (data === 1) {
            setTimeout(() => {
                resolve("getdata success");
            }, 1000);
        } else {
            setTimeout(() => {
                reject("getdata error");
            }, 1000);
        }
    });
}

// 方法1
// 缺点:如果有多个testAwait,需要多个try..catch,代码冗余!
const testAwait1 = async () => {
    try {
        let res = await getData(3);
        console.log(res);
    } catch (error) {
        console.log(res); //getdata error
    }
};

// 方法2
// 缺点:res把错误的结果和正确的结果都混在一起了,需要额外判断
const testAwait2 = async () => {
    let res = await getData(3)
        .then((r) => r)
        .catch((err) => err);
    console.log(res); //getdata error
};

// 方法3,对异步任务错误捕获封装
const awaitWraper = (promise) => {
    return [data, error] = await promise.then((res) => [res, null]).catch((error) => [null, error]);
};

const testAwait3 = async () => {
    const [res, error] = await awaitWraper(getData(3));
    console.log(res); //null
    console.log(error); //getdata error
};

参考:https://cloud.tencent.com/developer/article/1470715

# 015: ES6 Class 实现原理(Babel 是如何编译 class 的)