https://github.com/mqyqingfeng/Blog/issues/98
该文章含读程序及promise的相关介绍:https://www.cnblogs.com/lunlunshiwo/p/8852984.html
首先,Promise是一个对象,如同其字面意思一样,代表了未来某时间才会知道结果的时间,不受外界因素的印象。
Promise一旦触发,其状态只能变为fulfilled或者rejected,并且已经改变不可逆转。
Promise的构造函数接受一个函数作为参数,该参数函数的两个参数分别为resolve和reject,其作用分别是将Promise的状态由pending转化为fulfilled或者rejected,并且将成功或者失败的返回值传递出去。then有两个函数作为Promise状态改变时的回调函数,当Promise状态改变时接受传递来的参数并调用相应的函数。then中的回调的过程为异步操作。
catch方法是对.then(null,rejectFn)的封装(语法糖),用于指定发生错误时的回掉函数。一般来说,建议不要再then中定义rejected状态的回调函数,应该使用catch方法代替。
all和race都是竞速函数,all结束的时间取决于最慢的那个,其作为参数的Promise函数一旦有一个状态为rejected,则总的Promise的状态就为rejected;而race结束的时间取决于最快的那个,一旦最快的那个Promise状态发生改变,那个其总的Promise的状态就变成相应的状态,其余的参数Promise还是会继续进行的。
1.回调地狱引发的问题:
(1) 难以复用:回调的顺序确定下来之后,想对其中的某些环节进行复用也很困难,牵一发而动全身。
(2)堆栈信息被断开:
我们知道,JavaScript 引擎维护了一个执行上下文栈,当函数执行的时候,会创建该函数的执行上下文压入栈中,当函数执行完毕后,会将该执行上下文出栈。
如果 A 函数中调用了 B 函数,JavaScript 会先将 A 函数的执行上下文压入栈中,再将 B 函数的执行上下文压入栈中,当 B 函数执行完毕,将 B 函数执行上下文出栈,当 A 函数执行完毕后,将 A 函数执行上下文出栈。
这样的好处在于,我们如果中断代码执行,可以检索完整的堆栈信息,从中获取任何我们想获取的信息。
可是异步回调函数并非如此,比如执行 fs.readdir
的时候,其实是将回调函数加入任务队列中,代码继续执行,直至主线程完成后,才会从任务队列中选择已经完成的任务,并将其加入栈中,此时栈中只有这一个执行上下文,如果回调报错,也无法获取调用该异步操作时的栈中的信息,不容易判定哪里出现了错误。
此外,因为是异步的缘故,使用 try catch 语句也无法直接捕获错误。
(不过 Promise 并没有解决这个问题)
(3)借助外层变量
当多个异步计算同时进行,比如这里遍历读取文件信息,由于无法预期完成顺序,必须借助外层作用域的变量,比如这里的 count、errored、stats 等,不仅写起来麻烦,而且如果你忽略了文件读取错误时的情况,不记录错误状态,就会接着读取其他文件,造成无谓的浪费。此外外层的变量,也可能被其它同一作用域的函数访问并且修改,容易造成误操作。
之所以单独讲讲回调地狱,其实是想说嵌套和缩进只是回调地狱的一个梗而已,它导致的问题远非嵌套导致的可读性降低而已。
2.Promise 的局限性
(1)错误被吃掉
首先我们要理解,什么是错误被吃掉,是指错误信息不被打印吗?
并不是,举个例子:
throw new Error('error');
console.log(233333);
在这种情况下,因为 throw error 的缘故,代码被阻断执行,并不会打印 233333,再举个例子:
const promise = new Promise(null);
console.log(233333);
以上代码依然会被阻断执行,这是因为如果通过无效的方式使用 Promise,并且出现了一个错误阻碍了正常 Promise 的构造,结果会得到一个立刻跑出的异常,而不是一个被拒绝的 Promise。
然而再举个例子:
let promise = new Promise(() => {
throw new Error('error')
});
console.log(2333333);
这次会正常的打印 233333
,说明 Promise 内部的错误不会影响到 Promise 外部的代码,而这种情况我们就通常称为 “吃掉错误”。
其实这并不是 Promise 独有的局限性,try..catch 也是这样,同样会捕获一个异常并简单的吃掉错误。
而正是因为错误被吃掉,Promise 链中的错误很容易被忽略掉,这也是为什么会一般推荐在 Promise 链的最后添加一个 catch 函数,因为对于一个没有错误处理函数的 Promise 链,任何错误都会在链中被传播下去,直到你注册了错误处理函数。
(2)单一值
Promise 只能有一个完成值或一个拒绝原因,然而在真实使用的时候,往往需要传递多个值,一般做法都是构造一个对象或数组,然后再传递,then 中获得这个值后,又会进行取值赋值的操作,每次封装和解封都无疑让代码变得笨重。
说真的,并没有什么好的方法,建议是使用 ES6 的解构赋值:
Promise.all([Promise.resolve(1), Promise.resolve(2)])
.then(([x, y]) => {
console.log(x, y);
});
(3)无法取消:Promise 一旦新建它就会立即执行,无法中途取消。
(4)无法得知pending状态:当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
3.promise.race
promise.race()方法也可以处理一个promise实例数组但它和promise.all()不同,从字面意思上理解就是竞速,那么理解起来上就简单多了,也就是说在数组中的元素实例那个率先改变状态,就向下传递谁的状态和异步结果。但是,其余的还是会继续进行的。
let p1 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('1s') //1s后输出
resolve(1)
},1000)
})
let p10 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('10s') //10s后输出
resolve(10) //不传递
},10000)
})
let p5 = new Promise((resolve)=>{
setTimeout(()=>{
console.log('5s') //5s后输出
resolve(5) //不传递
},5000)
})
Promise.race([p1, p10, p5]).then((res)=>{
console.log(res); // 最后输出
})
上述代码输出结果:1s 1 5s 10s
4.红绿灯问题题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promse 实现)
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
var light = function(timmer, cb){
return new Promise(function(resolve, reject) {
setTimeout(function() {
cb();
resolve();
}, timmer);
});
};
var step = function() {
Promise.resolve().then(function(){
return light(3000, red);
}).then(function(){
return light(2000, green);
}).then(function(){
return light(1000, yellow);
}).then(function(){
step();
});
}
step();
// const promise = new Promise((resolve, reject) => {
// // promise.then()
// if (1) {
// resolve(value)
// } else {
// reject(error)
// }
// promise.then((function(value){
// }, function(error) {
// }))
// })
// function timeout(ms) {
// return new Promise((resolve, reject) => {
// setTimeout(resolve, ms, 'done')
// })
// }
// timeout(100).then((value) => {
// console.log(value)
// })
// let promise = new Promise(function(resolve, reject) {
// console.log('Promise');
// resolve(1);
// });
// promise.then(function(value) {
// console.log(value);
// });
// console.log('Hi!');
// new Promise((resolve, reject) => {
// resolve(1);
// console.log(2);
// }).then(r => {
// console.log(r);
// });
// 上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。
// 这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
// 一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,
// 而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
// new Promise((resolve, reject) => {
// return reject(1);
// console.log(2);
// }).then(r => {
// console.log(r);
// }).catch(error => {
// console.log(error + '+++')
// })
// console.log(3)
// getJSON("/post/1.json").then(function(post) {
// return getJSON(post.commentURL);
// }).then(function (comments) {
// console.log("resolved: ", comments);
// }, function (err){
// console.log("rejected: ", err);
// });
// 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
// const promise = new Promise(function(resolve, reject) {
// resolve('ok');
// throw new Error('test');
// });
// promise
// .then(function(value) { console.log(value) })
// .catch(function(error) { console.log(error) });
// ok
// 上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。
// 因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
// 代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。
// 如果没有报错,则会跳过catch方法。
// var x = 1
// const someAsyncThing = function() {
// return new Promise(function(resolve, reject) {
// // 下面一行会报错,因为x没有声明
// resolve(x + 2);
// });
// };
// someAsyncThing()
// .catch(function(error) {
// console.log('oh no', error);
// })
// .then(function() {
// console.log('carry on');
// });
// oh no [ReferenceError: x is not defined]
// carry on
// 代码因为没有报错,跳过了catch方法,直接执行后面的then方法。
// 此时,要是then方法里面报错,就与前面的catch无关了。
// Promise.resolve()
// .catch(function(error) {
// console.log('oh no', error);
// })
// .then(function() {
// console.log('carry on');
// });
// // carry on
// // catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。
// const someAsyncThing = function() {
// return new Promise(function(resolve, reject) {
// // 下面一行会报错,因为x没有声明
// resolve(x + 2);
// });
// };
// someAsyncThing().then(function() {
// return someOtherAsyncThing();
// }).catch(function(error) {
// console.log('oh no', error);
// // 下面一行会报错,因为 y 没有声明
// y + 2;
// }).then(function() {
// console.log('carry on');
// });
// // oh no [ReferenceError: x is not defined]
// 第二个catch方法用来捕获前一个catch方法抛出的错误。
// const someAsyncThing = function() {
// return new Promise(function(resolve, reject) {
// // 下面一行会报错,因为x没有声明
// resolve(x + 2);
// });
// };
// someAsyncThing().then(function() {
// return someOtherAsyncThing();
// }).catch(function(error) {
// console.log('oh no', error);
// // 下面一行会报错,因为y没有声明
// y + 2;
// }).catch(function(error) {
// console.log('carry on', error);
// });
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
// finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。
// 这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
// 上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,
// 该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。
// 该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,
// 因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
// const p1 = new Promise((resolve, reject) => {
// resolve('hello');
// })
// .then(result => result)
// .catch(e => e);
// const p2 = new Promise((resolve, reject) => {
// throw new Error('报错了');
// })
// .then(result => result)
// .catch(e => e);
// Promise.all([p1, p2])
// .then(result => console.log(result))
// .catch(e => console.log(e));
// // ["hello", Error: 报错了]
// 如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
// const p1 = new Promise((resolve, reject) => {
// resolve('hello');
// })
// .then(result => result);
// const p2 = new Promise((resolve, reject) => {
// throw new Error('报错了');
// })
// .then(result => result);
// Promise.all([p1, p2])
// .then(result => console.log(result))
// .catch(e => console.log(e));
// // Error: 报错了
// 需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,
// 而不是在下一轮“事件循环”的开始时。
// 上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,
// Promise.resolve()在本轮“事件循环”结束时执行,
// console.log('one')则是立即执行,因此最先输出。
// setTimeout(function () {
// console.log('three');
// }, 0);
// Promise.resolve().then(function () {
// console.log('two');
// });
// console.log('one');
// one
// two
// three
// // const p = Promise.reject('出错了');
// // 等同于
// const p = new Promise((resolve, reject) => reject('出错了'))
// p.then(null, function (s) {
// console.log(s)
// });
// // 出错了
// 上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
// 注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
// 这一点与Promise.resolve方法不一致。
// Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,
// 而是thenable对象。
// const thenable = {
// then(resolve, reject) {
// reject('出错了');
// }
// };
// Promise.reject(thenable)
// .catch(e => {
// console.log(thenable) // { then: [Function: then] }
// console.log(e === thenable)
// })
// // true
// 应用
// (1)加载图片:我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};