Open CruxF opened 6 years ago
这篇博文是根据慕课网教程整理而来,内容几乎都会是讲师的原话,外带一些自己的理解。
这个英语单词翻译成中文意思就是:许诺;允诺;有可能。因此从字面上就可以知道它代表了即将要发生的事情,从而联想到了JavaScript中异步程序。
按照它的实际用途来看主要有以下几点
根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。
借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。
从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。
咳咳,说重点,以下才是Promise诞生的原因
a(function (resultsFromA) { b(resultsFromA, function (resultsFromB) { c(resultsFromB, function (resultsFromC) { d(resultsFromC, function (resultsFromD) { e(resultsFromD, function (resultsFromE) { f(resultsFromE, function (resultsFromF) { console.log(resultsFromF); }) }) }) }) }) });
【优点】
【状态】
new Promise( /* 执行器 executor */ function (resolve, reject) { // 一段耗时很长的异步操作 resolve(); // 数据处理完成 reject(); // 数据处理出错 } ).then(function A() { // 成功,下一步 }, function B() { // 失败,做相应处理 });
首先看课程里提供的方法
// 事件侦听与响应 document.getElementById('start').addEventListener('click', start, false); function start() { // 响应事件,进行相应的操作 } // jQuery 用 `.on()` 也是事件侦听 $('#start').on('click', start); // 回调,比较常见的有ajax $.ajax('http://baidu.com', { success: function (res) { // 这里就是回调函数了 } }); // 或者在页面加载完毕后回调 $(function () { // 这里也是回调函数 });
以上是课程稍微提到的方法,下面请看阮一峰老师的进一步说明,面试的时候可以使劲说啦,传送门在此。
console.log('here we go'); new Promise(resolve => { setTimeout(() => { resolve('hello'); console.log(123); }, 2000); }) .then(name => { console.log(name + ' world'); });
以上代码和课程稍微有些不同,目的是和定时器做一些对比,以此发现一点什么。
console.log('here we go'); setTimeout(() => { callback("hello"); console.log(123); }, 2000) function callback(name) { console.log(name + ' world'); }
通过以上两段代码的运行结果比较,可以浅显的得出:resolve()状态引发的then()是异步的,更多的我暂时就不知道啦。
console.log('here we go'); new Promise(resolve => { setTimeout(() => { resolve('hello'); }, 2000); }) .then(value => { console.log(value); return new Promise(resolve => { setTimeout(() => { resolve('world'); }, 2000); }); }) .then(value => { console.log(value + ' world'); });
这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。
console.log('start'); let promise = new Promise(resolve => { setTimeout(() => { console.log('the promise fulfilled'); resolve('hello, world'); }, 1000); }); setTimeout(() => { promise.then(value => { console.log(value); }); }, 3000);
讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。
console.log('here we go'); new Promise(resolve => { setTimeout(() => { resolve('hello'); }, 2000); }) .then(value => { console.log(value); console.log('everyone'); (function () { return new Promise(resolve => { setTimeout(() => { console.log('Mr.Laurence'); resolve('Merry Xmas'); }, 2000); }); }()); return false; }) .then(value => { console.log(value + ' world'); });
我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。
要想调用resolve()里的数据,只要这么写就可以了
console.log('here we go'); new Promise(resolve => { setTimeout(() => { resolve('hello'); }, 2000); }) .then(value => { console.log(value); console.log('everyone'); (function () { return new Promise(resolve => { setTimeout(() => { console.log('Mr.Laurence'); resolve('Merry Xmas'); }, 2000); }); }()).then(value => { console.log(value + ' world'); }); })
then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码
console.log('start'); new Promise(resolve => { console.log('Step 1'); setTimeout(() => { resolve(100); }, 1000); }) .then(value => { return new Promise(resolve => { console.log('Step 1-1'); setTimeout(() => { resolve(110); }, 1000); }) .then(value => { console.log('Step 1-2'); return value; }) .then(value => { console.log('Step 1-3'); return value; }); }) .then(value => { console.log(value); console.log('Step 2'); });
解套后的代码为:
console.log('start'); new Promise(resolve => { console.log('Step 1'); setTimeout(() => { resolve(100); }, 1000); }) .then(value => { return new Promise(resolve => { console.log('Step 1-1'); setTimeout(() => { resolve(110); }, 1000); }) }) .then(value => { console.log('Step 1-2'); return value; }) .then(value => { console.log('Step 1-3'); return value; }) .then(value => { console.log(value); console.log('Step 2'); });
两段代码的执行结果一致(话说此时你们清楚的知道结果是啥吗)。
看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?
// 问题一 doSomething() .then(function () { return doSomethingElse(); }) .then(finalHandler); //执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultDoSomethingELlse) // 问题二 doSomething() .then(function () { doSomethingElse(); }) .then(finalHandler); //执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(undefined) //注意:doSomethingElse(undefined)和finalHandler(undefined)同时执行 // 问题三 doSomething() .then(doSomethingElse()) .then(finalHandler); //执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultOfDoSomething) //注意:doSomethingElse(undefined)和doSomething()同时执行 // 问题四 doSomething() .then(doSomethingElse) .then(finalHandler); //执行流程为doSomething ==> doSomethingElse(resultOfDoSomething) ==> finalHandler(resultOfDoSomethingElse)
因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。
谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。
function getNumber() { var p = new Promise(function(resolve, reject) { //做一些异步操作 setTimeout(function() { var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if(num <= 5) { resolve(num); } else { reject('数字太大了'); } }, 2000); }); return p; } getNumber().then(function(data) { console.log('resolved'); console.log(data); }, function(reason) { console.log('rejected'); console.log(reason); });
getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。
运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。
另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:
function getNumber() { var p = new Promise(function(resolve, reject) { //做一些异步操作 setTimeout(function() { var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if(num <= 5) { resolve(num); } else { reject('数字太大了'); } }, 2000); }); return p; } getNumber().then(function(data) { console.log('resolved'); console.log(data); }).catch(function(reason) { console.log('rejected'); console.log(reason); });
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下
// 测试代码1 getNumber().then(function(data) { console.log(name()); console.log('resolved'); console.log(data); }, function(reason, data) { console.log('rejected'); console.log(reason); }); // 测试代码2 getNumber().then(function(data) { console.log(name()); console.log('resolved'); console.log(data); }).catch(function(reason) { console.log('rejected'); console.log(reason); });
在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。
Promise.all()具有批量执行的特点,用于将多个Promise实例,包装成一个新的Promise实例,返回的就是普通Promise。
它接受一个数组作为参数,数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态的改变。
当所有子Promise都完成,那么返回新的Promise才认为是完成了,返回值是全部值的数组;有任何一个失败,则新的Promise就认为失败了,返回值是第一个失败的子Promise的结果。下面看代码:
console.log('here we go'); Promise.all([1, 2, 3]).then(all => { console.log('1:', all); return Promise.all([function() { console.log('ooxx'); }, 'xxoo', false]); }).then(all => { console.log('2:', all); let p1 = new Promise(resolve => { setTimeout(() => { resolve('I\'m P1'); }, 1500); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('I\'m P2'); }, 1000); }); let p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('I\'m P3'); }, 3000); }); return Promise.all([p1, p2, p3]); }).then(all => { console.log('all', all); }).catch(err => { console.log('Catch:', err); });
真是让人头大的代码,建议各位还是先来看看这篇吧——大白话讲解Promise
有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行,用代码解释就是如下这样的:
let promise = doSomething(); promise = promise.then(doSomethingElse); promise = promise.then(doSomethingElse2); promise = promise.then(doSomethingElse3); .........
实现队列方式一:使用forEach()
function queue(things) { let promise = Promise.resolve(); things.forEach(thing => { promise.then(() => { return new Promise(resolve => { doThing(thing, () => { resolve(); }); }); }); }); return promise; } queue(['lots', 'of', 'things', ....]);
【注意】 常见错误:没有把then()产生的新Promise实例赋给promise,没有生成队列。
实现队列方式二:使用reduce()
function queue(things) { return things.reduce((promise, thing) => { return promise.then(() => { return new Promise(resolve => { doThing(thing, () => { resolve(); }); }); }); }, Promise.resolve()); } queue(['lots', 'of', 'things', ....]);
Promise.resolve()返回一个fulfilled状态的Promise实例,或原始的Promise实例,具有如下特点:
console.log('start'); Promise.resolve().then(() => { console.log('Step 1'); return Promise.resolve('Hello'); }).then(value => { console.log(value, 'World'); return Promise.resolve(new Promise(resolve => { setTimeout(() => { resolve('Good'); }, 2000); })); }).then(value => { console.log(value, ' evening'); return Promise.resolve({ then() { console.log(', everyone'); } }) })
Promise.reject()除了不认thenable,其他的特点都和Promise.resolve()类似,请看如下代码:
let promise = Promise.reject('something wrong'); promise.then(() => { console.log('it\'s ok'); }).catch(() => { console.log('no, it\'s not ok'); return Promise.reject({ then() { console.log('it will be ok'); }, catch() { console.log('not yet'); } }); });
类似Promise.all(),区别在于它有任意一个完成就算完成,观察以下代码:
console.log('start'); let p1 = new Promise(resolve => { // 这是一个长时间的调用 setTimeout(() => { resolve('I\'m P1'); }, 10000); }); let p2 = new Promise(resolve => { // 这是个稍短的调用 setTimeout(() => { resolve('I\'m P2'); }, 2000) }); Promise.race([p1, p2]).then(value => { console.log(value); });
Promise.race()常见用法是把异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。这里可能说的有点抽象,希望来这里看一看——大白话讲解Promise,那么很容易就能明了。
// 弹出窗体 let confirm = popupManager.confirm('您确定么?'); confirm.promise.then(() => { // do confirm staff }).catch(() => { // do cancel staff }); // 窗体的构造函数 class Confirm { constructor() { this.promise = new Promise((resolve, reject) => { this.confirmButton.onClick = resolve; this.cancelButton.onClick = reject; }) } }
呃呃,IE那块我选择不鸟它了,一个连它爸爸都嫌弃的浏览器也是没救了。还有最新的异步函数async和await,不说了,困得一批,劳资下班睡觉去。
最后的最后,强烈推荐一篇反复出现的博文和那位博主,有非常值得学习的地方:大白话讲解Promise
前言
这篇博文是根据慕课网教程整理而来,内容几乎都会是讲师的原话,外带一些自己的理解。
Promise是什么
这个英语单词翻译成中文意思就是:许诺;允诺;有可能。因此从字面上就可以知道它代表了即将要发生的事情,从而联想到了JavaScript中异步程序。
按照它的实际用途来看主要有以下几点
Promise产生的背景
根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。
借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。
从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。
咳咳,说重点,以下才是Promise诞生的原因
Promise的概念和优点
【优点】
【状态】
Promise的基本语法
异步操作的常见方法
首先看课程里提供的方法
以上是课程稍微提到的方法,下面请看阮一峰老师的进一步说明,面试的时候可以使劲说啦,传送门在此。
Promise一个简单的例子
以上代码和课程稍微有些不同,目的是和定时器做一些对比,以此发现一点什么。
通过以上两段代码的运行结果比较,可以浅显的得出:resolve()状态引发的then()是异步的,更多的我暂时就不知道啦。
Promise两步执行的范例
这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。
对已经完成的Promise执行then()
讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。
then()不返回Promise
我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。
要想调用resolve()里的数据,只要这么写就可以了
then()解析
then()的嵌套
then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码
解套后的代码为:
两段代码的执行结果一致(话说此时你们清楚的知道结果是啥吗)。
Promise小测试
看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?
Promise错误处理
因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。
谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。
getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。
运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。
另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下
在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。
Promise.all()解析
Promise.all()具有批量执行的特点,用于将多个Promise实例,包装成一个新的Promise实例,返回的就是普通Promise。
它接受一个数组作为参数,数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态的改变。
当所有子Promise都完成,那么返回新的Promise才认为是完成了,返回值是全部值的数组;有任何一个失败,则新的Promise就认为失败了,返回值是第一个失败的子Promise的结果。下面看代码:
真是让人头大的代码,建议各位还是先来看看这篇吧——大白话讲解Promise
Promise实现队列
有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行,用代码解释就是如下这样的:
实现队列方式一:使用forEach()
【注意】 常见错误:没有把then()产生的新Promise实例赋给promise,没有生成队列。
实现队列方式二:使用reduce()
Promise.resolve()解析
Promise.resolve()返回一个fulfilled状态的Promise实例,或原始的Promise实例,具有如下特点:
Promise.reject()解析
Promise.reject()除了不认thenable,其他的特点都和Promise.resolve()类似,请看如下代码:
Promise.race()解析
类似Promise.all(),区别在于它有任意一个完成就算完成,观察以下代码:
Promise.race()常见用法是把异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。这里可能说的有点抽象,希望来这里看一看——大白话讲解Promise,那么很容易就能明了。
现实生活中的Promise应用
尾声
呃呃,IE那块我选择不鸟它了,一个连它爸爸都嫌弃的浏览器也是没救了。还有最新的异步函数async和await,不说了,困得一批,劳资下班睡觉去。
最后的最后,强烈推荐一篇反复出现的博文和那位博主,有非常值得学习的地方:大白话讲解Promise