ES6快速理解Promise

本篇文章来源于https://mp.weixin.qq.com/s/tetfPizYwMtr-XlBRfZAQA,主要帮助学习者快速理解认识Promise。

Promise是ES6中的特性,现在很多前端框架像AngularJS,Vue等在HTTP请求之后都是返回的Promise处理,因此Promise是必须要掌握的一个知识点。

案例1:

Promise构造函数是同步执行的,promise.then中的函数是异步执行的。

const promise = new Promise((resolve,reject) => {
    console.log(1);
    resolve();
    console.log(2);
});

promise.then(() => {
    console.log(3);
});

console.log(4);


/**运行结果**/
// => 1
// => 2
// => 4
// => 3

案例2:

const first = () => (new Promise((resolve,reject) => {
    console.log(3);
    let p = new Promise((resolve,reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        },0);
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));

first().then((arg) => {
    console.log(arg);
});
console.log(4);


/**打印结果**/
// => 3
// => 7
// => 4
// => 1
// => 2
// => 5

解析:

第一轮事件循环,先执行宏任务,主script,new Promise立即执行,输出3,执行p这个new Promise操作,输出7,发现setTimeout,将回调函数放入下一轮任务队列(Event Quene),p的then,暂时命名为then1,放入微任务队列,且first也由then,命名为then2,放入微任务队列。执行console.log(4),输出4,宏任务执行结束。

再执行微任务,执行then1,输出1,执行thne2,输出3

第一轮事件循环结束,开始执行第二轮。第二轮事件循环先执行宏任务里面的,也就是setTimeout的回调,输出5。resolve(6)不会生效,因为p的Promise状态一旦改变就不会再变化了。

案例3:

const promise1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('success');
    },1000);
});

const promise2 = promise1.then(() => {
    throw new Error("error!!!");
});

console.log('promise1',promise1);
console.log('promise2',promise2);

setTimeout(() => {
    console.log('promise1',promise1);
    console.log('promise2',promise2);
},2000);

/**运行结果**/
// => promise1 Promise {<pending>}
// => promise2 Promise {<pending>}
// => Uncaught (in promise) Error: error!!! at <anonymous>
// => promise1 Promise {<resolved>: "success"}
// => promise2 Promise {<rejected>: Error: error!!! at <anonymous>}

解析:

promise有3种状态:pending、fulfilled或rejected。状态改变只能是pending->fulfilled或者pending->rejected,状态一旦改变则不能再变。上面promise2并不是promise1,而是返回的一个新的Promise实例。

案例4:

const promise = new Promise((resolve,reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});
promise.then((res) => {
    console.log('then: ',res);
}).catch((err) => {
    console.log('catch: ',err);
});

/**运行结果**/
// => then: success1

解析:

构造函数中的resolve或reject只有第一次执行有效,多次调用没有任何作用,promise状态一旦改变则不能再变。

案例5:

Promise.resolve(1)
    .then((res) => {
        console.log(res);
        return 2;
    })
    .catch((err) => {
        return 3;
    })
    .then((res) => {
        console.log(res);
    });

/**运行结果**/
// => 1
// => 2

解析:

promise可以链式调用。提起链式调用我们通常会想到通过return this实现,不过Promise并不是这样实现的。promise每次调用.then或者.catch都会返回一个新的promise,从而实现了链式调用。

案例6:

const promise = new Promise((resolve,reject) => {
    setTimeout(() => {
        console.log('once');
        resolve('success');
    },1000);
});
const start = Date.now();
promise.then((res) => {
    console.log(res,Date.now() - start);
});
promise.then((res) => {
    console.log(res,Date.now() - start);
});

/**运行结果**/
// => once
// => success 1004
// => success 1005

解析:

promise的.then或者.catch可以被调用多次,但是这里Promise构造函数只执行一次。或者说promise内部状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch都会直接拿到该值。

案例7:

Promise.resolve()
    .then(() => {
        return new Error("error!!!");
    })
    .then((res) => {
        console.log('then: ', res);
    })
    .catch((err) => {
        console.log('catch: ',err);
    });

/**运行结果**/
// => then: Error: error!!! at <anonymous>
// 此时:Promise {<fulfilled>: undefined}

解析:

.then或者.catch中return一个error对象并不会抛出错误,所以不会被后续的.catch捕获,需要改成其中一种:

  1.  return Promise.reject(new Error('error!!!'))
  2.  throw new Error('error!!!')

因为返回任意一个非promise的值都会被包裹到promise对象,即return new Error('error!!!')等价于return Promise.resolve(new Error('error!!!'))。

案例8:

const promise = Promise.resolve()
    .then(() => {
        return promise;
    })
promise.catch(console.error);


/**运行结果**/

// =>TypeError: Chaining cycle detected for promise #<Promise>



/**类似于**/
process.nextTick(function tick(){
    console.log("tick");
    process.nextTick(tick);
});

解析:.then或.catch返回的值不能是promise本身,否则会造成死循环。

案例9:

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log);

/**运行结果**/
// => 1

解析:.then或者.catch的参数期望是函数,传入非函数则会发生值穿透。

案例10:

Promise.resolve()
    .then(function success(res) {
        throw new Error('error');
    },function fail1(e) {
        console.error('fail1: ',e);
    })
    .catch(function fail2(e) {
        console.error('fail2: ',e);
    });

/**运行结果**/
// => fail2: Error: errpr at success (<anonymous ...>)


/**也等价于**/
Promise.resolve()
    .then(function success1(res) {
        throw new Error('error');
    },function fail1(e) {
        console.error('fail1: ',e);
    })
    .then(function success2(res) {
    },function fail2(e){
        console.error('fail2: ',e);
    });

解析:

.then可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。

.catch是.then第二个参数的简便写法,但是它们用法上有一点需要注意:.then的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的.catch可以捕获之前的错误。

案例11:

process.nextTick(() => {
    console.log('nextTick');
});

Promise.resolve()
    .then(() => {
        console.log('then');
    })
    setImmediate(() => {
        console.log('setImmediate');
    });
comsole.log('end');

/**运行结果**/
// => end
// => nextTick
// => then
// => setImmediate

解析:

process.nextTick和promise.then都属于microtask,而setImmediate属于macrotask,在事件循环的check阶段执行。事件循环的每个阶段(macrotask)之间都会执行microtask,事件循环的开始会先执行一次microtask。

扩展:node.js中 的process.nextTick()setImmediate使用案例。

案例12:

红灯3秒亮一次,绿灯2秒亮一次,黄灯1秒亮一次;如何使用Promise让三个灯不断交替重复亮灯?

function red() {
    console.log("红灯");
}
function green() {
    console.log("绿灯");
}
function yellow() {
    console.log("黄灯");
}

let myLight = (timer,cb) =>{
    return new Promise((resolve) => {
        setTimeout(() => {
            cb();
            resolve();
        },timer);
    });
};

let myStep = () => {
    Promise.resolve().then(() => {
        return myLight(3000,red);
    })
    .then(() => {
        return myLight(2000,green);
    })
    .then(() => {
        return myLight(1000,yellow);
    }).then(()=>{
        myStep();
    });
};
myStep();

解析思路:

换句话说,就是红灯亮起时,承诺2秒后亮绿灯,绿灯亮起时承诺1s秒后亮黄灯,黄灯亮起时,承诺3秒后亮红灯......这显然是一个Promise链式调用,看到这里你心里或许就有思路了,我们需要将我们的每一个亮灯动作写在then()方法中,同时返回一个新的Promise,并将其状态由pending设置为fulfilled,允许下一盏灯亮起。

案例13:

const timeout = ms => new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve();
    },ms);
});

const ajax1 = () => timeout(2000).then(() => {
    console.log("1");
    return 1;
});

const ajax2 = () => timeout(1000).then(() => {
    console.log("2");
    return 2;
});

const ajax3 = () => timeout(2000).then(() => {
    console.log("3");
    return 3;
});

const mergePromise = ajaxArray => {
    // 在这里实现你的代码
    // 保存数组中的函数执行后的结果
    var data = [];

    // Promise.resolve方法调用时不带参数,直接返回一个resolved状态的Promise对象
    var sequence = Promise.resolve();
    ajaxArray.forEach(item => {
        // 第一次的then方法用来执行数组中的每个函数
        // 第二次的then方法接收数组中的函数执行后返回的结果
        // 并把结果添加到data中,然后把data返回
        sequence = sequence.then(item).then(res => {
            data.push(res);
            return data;
        });
    });
    // 遍历结束后,返回一个Promise,也就是sequence,他的[[PromiseValue]]值就是data,
    // 而data(保存数组中的函数执行后的结果)也会作为参数,传入下次调用的then方法中。
    return sequence;
};
mergePromise([ajax1,ajax2,ajax3]).then(data => {
    console.log("done");
    console.log(data); // data为[1,2,3]
});


/**要求分别输出**/
// => 1
// => 2
// => 3
// => done
// => [1,2,3]

案例14:

现有8个图片资源的url,已经存储在数组urls中,且已有一个函数function loading,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。

要求:任何时刻同时下载的链接数量不可以超过3个。

var urls = ['images/img1.jpg','images/img2.jpg',......]; // 省略了

function loadImg(url){
    return new Promise((resolve,reject) =>{
        const img  = new Image();
        img.onload = () => {
            console.log("一张图片加载完成");
            resolve();
        }
        img.onerror = reject;
        img.src     = url;
    });
}

/**实现**/

function limitLoad(urls,handler,limit) {
    // 对数组做一个拷贝
    const sequence = [...urls];
    let   promises = [];
    // 并发请求到最大数
    promises = sequence.splice(0,limit).map((url,index) => {
        // 这里返回的index是任务在promises的脚标,用于在Promise.race之后找到完成的任务脚标
        return handler(url).then(() => {
            return index;
        });
    });
    // 利用数组的reduce方法来以队列的形式执行
    return sequence.reduce((last,url,currentIndex) => {
        return last.then(() => {
            // 返回最快改变状态的Promise
            return Promise.race(promises);
        }).catch(err =>{
            // 这里的catch不仅用来捕获前面的then方法抛出的错误
            // 更重要的是防止中断整个链式调用
            console.error(err);
        }).then((res) => {
            // 用新的Promise替换掉最快改变状态的Promise
            promises[res] = handler(sequence[currentIndex]).then(()=>{
                return res;
            });
        });
    },Promise.resolve()).then(() =>{
        return Promise.all(promises);
    });
}

limitload(urls,loadImg,3);

// 因为limitLoad函数也返回一个Promise,所以当所有图片加载完成后,可以继续链式调用
limitLoad(urls,loadImg,3).then(() =>{
    console.log("所有图片加载完成");
}).catch(err =>{
    console.log(err);
});

解析:

用Promise来实现就是,先并发请求3个图片资源,这样可以得到3个Promise,组成一个数组promises,然后不断调用Promise.race来返回最快改变形状的Promise,然后从数组promises中删除这个Promise对象,再加入一个新的Promise。直到全部的url被取完,最后再使用Promise.all来处理一遍数组promises中没有改变状态的Promise。

案例15:

封装一个异步加载图片的方法。

function loadImageAsync(url){
    return new Promise(function(resolve,reject) {
        var image    = new Image();
        image.onload = function(){
            resolve(image);
        };
        image.onerror = function(){
            reject(new Error("Could not load at "+url));
        };
        image.src = url;
    });
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: ES6是ECMAScript 2015的简称,是JavaScript的一个重要的更新版本。深入理解ES6 PDF是一本通过深入研究ES6标准来理解其中各种语法和特性的书籍。 这本书分为十八章,从ES6的基础语法和新特性入手,详细讲解了ES6的语法、语义和使用方法。其中包括了箭头函数、简化的对象字面量、模板字面量、解析器、新的控制结构、类和继承、生成器和迭代器、异步操作、Symbol和迭代器、集合和迭代器、Map和Set、新的数组方法、Promise和异步操作、代理和反射、模块化和导入机制,每一章都是围绕特定的内容展开细致的解读。 对于学习ES6的开发者来说,这本书是一份非常有价值的资料。读者可以从中深入了解ES6的语言架构和语法规范,更加深入地理解JavaScript的基础和高级语言特性。对于从ES5升级到ES6的开发者来说,该书是学习ES6语言的最佳手册,还可以通过实例代码快速掌握ES6的语言特性。 总之,深入理解ES6 PDF是一本非常专业且详细的技术书籍,可以帮助读者深入了解ES6语言特性,并掌握新的编程技巧。 ### 回答2: ES6是Javascript的下一代语言标准,它引入了许多新的语法、特性和API,使得编程变得更加简单、高效、灵活和快捷。深入理解ES6 PDF是一本ES6入门指南,它帮助读者系统地学习ES6的核心知识和技能,了解ES6的语法、模块、类、迭代器、生成器、Promise等核心概念及其实践应用。 阅读深入理解ES6 PDF,首先需要了解ES6的基本语法和新特性,包括箭头函数、模板字符串、解构赋值、默认参数、rest参数等。其次,需要学习ES6的模块化设计,了解如何实现模块导出、导入、依赖管理等功能,以便更好地组织代码和提高代码的复用性。另外,深入理解ES6 PDF也介绍了ES6的面向对象编程特性,包括类的声明、方法的定义、继承、多态等,以及ES6的迭代器、生成器、Promise等核心概念及其实践应用,使得读者可以更加深入地掌握ES6的精髓和应用。 综上所述,深入理解ES6 PDF是一本非常好的ES6入门指南,它帮助读者了解ES6的核心知识和技能,带领读者建立ES6的编程思想和风格,以便更好地应对现代Web应用开发的挑战,同时也是学习ES6语言的必备指南。 ### 回答3: 《深入理解ES6》是一本详细介绍ES6新特性的书籍。它主要介绍了模板字符串、解构赋值、箭头函数、类、模块化开发等ES6的新语法。通过深入阅读这本书,能够更好地理解和运用ES6的新特性。 其中,模板字符串是ES6引入的一个非常重要的新特性,它可以让我们更方便地处理字符串拼接和换行,同时还支持常用的字符串操作方法。 解构赋值则是ES6中的另一个重要特性,它可以让我们更快捷地从一个对象或数组中提取出需要的值,这对于提高代码的可读性和复用性都非常有帮助。 箭头函数是ES6中的又一个新特性,它可以让我们更愉悦地编写函数,并且它的this指向也更加方便和易懂。同时也是优化了ES5语言中函数语法臃肿和拖沓的问题。 类是ES6中一个重要的新特性,它可以让我们更加方便地实现面向对象编程,这是在ES5中较为繁琐的过程,同时也减少了ES5类的一些使用致命局限。 最后,模块化开发也是ES6中的一个重要特性,它可以让我们更加方便地组织和管理代码,同时也减少了代码间的相互污染,使得代码更加易于维护和扩展。 总之,《深入理解ES6》这本书为我们深入了解ES6的新特性提供了很好的指导和方向,并且还有配套的演示代码,非常值得一读。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值