面试问到 Promise,这样回答最完美了
promise是什么?Promise是异步编程的一种解决方案,比传统的回调函数和事件更合理和强大。所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事情(通常是一个异步操作)。从语法上说,Promise是一个对象,从他可以获取异步操作的消息。特点:对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulf...
promise是什么?
Promise是异步编程的一种解决方案,比传统的回调函数和事件更合理和强大。
所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事情(通常是一个异步操作)。从语法上说,Promise是一个对象,从他可以获取异步操作的消息。
特点:
-
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(以失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来。
-
一旦状态改变,就不会再变,任何时候都是可以得到这个结果的。Promise对象的状态改变只有两种可能:*从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就会凝固,不会再变了。再对Promise对象添加回调函数也会立即得到这个结果。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来。
缺点:
首先无法取消Promise,一旦新建他就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部跑出的错误无法反应到外部。当pending的时候,无法知道进展到了哪一步。
基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。
const promise = new Promise(function(resolve, reject) {
if(success) {
resolve(value)
} else {
reject(error)
}
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从"未完成"变成"成功"。(即从pending变为resolved)。在异步操作成功的时候调用,并将异步操作结果作为参数传递出去;
reject函数的作用是,将promise对象的状态从"未完成"变成"失败"(即从pending变为rejected)。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成后,可以用then方法分别指定resolve状态和rejected状态的回调函数。
promise.then(function(value) {
}, function(error) {
})
then方法可以接受两个回调函数作为参数,
第一个回调函数是promise对象的状态变为resolved的时候调用,
第二个回调函数是promise对象的状态变为rejected时调用。
其中第二个函数是可选的,不一定需要提供。
这两个函数都接受Promise对象传出的值作为参数。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done')//setTimeout 传参
})
}
timeout(100).then((value) => {
console.log(value)//done
})
上面代码中,timeout方法返回一个Promise实例,表示一段时间后才会发生的结果。
过了指定的时间以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
Promise新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise')
resolve()
})
promise.then(function() {
console.log('resolved')
})
console.log('Hi')
//Promise
//Hi
//resolved
上面代码中,Promise新建后立即执行,所以首先输出的是Promise,然后then方法指定回调函数,将在当前脚本所有同步任务执行完成后才会执行,所以resolved最后输出。
如下是一个异步加载图片的例子:
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image()
image.onload = function() {
resolve(image)
}
image.onerror = function() {
reject(new Error('count not load...'))
}
image.src = url
})
}
上面代码中,使用Promise包装一个图片加载的异步操作,如果加载成功就调用resolve方法, 否则就调用rejected方法。
1. Promise.prototype.then()
Promise实例具有then方法,也就是说then方法时定义在原型对象上的。
它的作用是为Promise实例添加状态改变时的回调函数。
前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数(可选)。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)因此可以采用链式写法,即then方法后面再调用另一个then方法。
采用链式的then可以指定一组按照次序调用的回调函数。这时,前一个回调函数可能返回一个还是Promise对象(即有异步操作),这时候一个回调函数就会等该Promise对象的状态发生变化,才会被调用
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL)
}).then(function funcA() {
console.log("resolved:", comments)
}, function funcB(err) {
console.log("rejected:", err)
})
上面代码中,第一个then方法指定的回调函数,返回的是一个Promise对象。这时,第二个方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA, 如果状态变为rejected,就调用funcB.
2. Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数
getJSON('/post/1.json').then(function(posts) {
//...
}).catch(function() {
console.log('发生错误', error)
})
上面代码中,getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数。另外,then方法指定的回调函数,如果运行抛出错误,也会被catch方法捕获。
p.then(val => console.log('fulfilled:', val))
.catch(err => console.log('rejected', err))
//等同于
p.then(val => console.log('fulfilled:', val))
.then(null, err => {console.log('rejected:', err)})
如果Promise状态以及变成resolved,再抛出错误是无效的。因为Promise的状态一旦改变,就永久保持该状态,不会再变了。
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也就是说错误总会被下一个catch语句捕获。
3. Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。
4.Pomise.all的使用
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
具体代码如下:
let t1 = new Promise((resolve,reject)=>{
resolve("t1-success")
})
let t2 = new Promise((resolve,reject)=>{
resolve("t2-success")
})
let t3 =Promise.reject("t3-error");
Promise.all([t1,t2,t3]).then(res=>{
console.log(res)
}).catch(error=>{
console.log(error)
})
//打印出来是t3-error
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
let request = (time,id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`第${id}个请求${time / 1000}秒`)
}, time)
})
}
let p1 = request(3000,1)
let p2 = request(2000,2)
Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '第1个请求3秒', '第2个请求2秒' ]
}).catch((error) => {
console.log(error)
})
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
5、Promise.race的使用
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let f1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let f2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([f1, f2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。
举例:超时取消
我们来看一下如何使用Promise.race来实现超时机制。
当然XHR有一个 timeout 属性,使用该属性也可以简单实现超时功能,但是为了能支持多个XHR同时超时或者其他功能,我们采用了容易理解的异步方式在XHR中通过超时来实现取消正在进行中的操作。
1. 让Promise等待指定时间
首先我们来看一下如何在Promise中实现超时。
所谓超时就是要在经过一定时间后进行某些操作,使用 setTimeout 的话很好理解。
首先我们来串讲一个单纯的在Promise中调用 setTimeout 的函数。
//delayPromise.js
function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
delayPromise(ms) 返回一个在经过了参数指定的毫秒数后进行onFulfilled操作的promise对象,这和直接使用 setTimeout 函数比较起来只是编码上略有不同,如下所示。
setTimeout(function () {
alert("已经过了100ms!");
}, 100);
// == 几乎同样的操作
delayPromise(100).then(function () {
alert("已经过了100ms!");
});
在这里 promise对象 这个概念非常重要,请切记。
2. Promise.race中的超时
我们可以将刚才的 delayPromise 和其它promise对象一起放到 Promise.race 中来是实现简单的超时机制。
//simple-timeout-promise.js
function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function timeoutPromise(promise, ms) {
var timeout = delayPromise(ms).then(function () {
throw new Error('Operation timed out after ' + ms + ' ms');
});
return Promise.race([promise, timeout]);
}
函数 timeoutPromise(比较对象promise, ms) 接收两个参数,第一个是需要使用超时机制的promise对象,第二个参数是超时时间,它返回一个由 Promise.race 创建的相互竞争的promise对象。
之后我们就可以使用 timeoutPromise 编写下面这样的具有超时机制的代码了。
function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function timeoutPromise(promise, ms) {
var timeout = delayPromise(ms).then(function () {
throw new Error('Operation timed out after ' + ms + ' ms');
});
return Promise.race([promise, timeout]);
}
// 运行示例
var taskPromise = new Promise(function(resolve){
// 随便一些什么处理
var delay = Math.random() * 2000;
setTimeout(function(){
resolve(delay + "ms");
}, delay);
});
timeoutPromise(taskPromise, 1000).then(function(value){
console.log("taskPromise在规定时间内结束 : " + value);
}).catch(function(error){
console.log("发生超时", error);
});
虽然在发生超时的时候抛出了异常,但是这样的话我们就不能区分这个异常到底是_普通的错误_还是_超时错误_了。
为了能区分这个 Error 对象的类型,我们再来定义一个Error 对象的子类 TimeoutError。
扩展知识:定制Error对象
Error 对象是ECMAScript的内建(build in)对象。
但是由于stack trace等原因我们不能完美的创建一个继承自 Error 的类,不过在这里我们的目的只是为了和Error有所区别,我们将创建一个 TimeoutError 类来实现我们的目的。
在ECMAScript6中可以使用 class 语法来定义类之间的继承关系。
class MyError extends Error{
// 继承了Error类的对象
}
为了让我们的 TimeoutError 能支持类似 error instanceof TimeoutError 的使用方法,我们还需要进行如下工作。
//TimeoutError.js
function copyOwnFrom(target, source) {
Object.getOwnPropertyNames(source).forEach(function (propName) {
Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
});
return target;
}
function TimeoutError() {
var superInstance = Error.apply(null, arguments);
copyOwnFrom(this, superInstance);
}
TimeoutError.prototype = Object.create(Error.prototype);
TimeoutError.prototype.constructor = TimeoutError;
我们定义了 TimeoutError 类和构造函数,这个类继承了Error的prototype。
它的使用方法和普通的 Error 对象一样,使用 throw 语句即可,如下所示。
var promise = new Promise(function(){
throw TimeoutError("timeout");});promise.catch(function(error){ console.log(error instanceof TimeoutError);// true});
有了这个 TimeoutError 对象,我们就能很容易区分捕获的到底是因为超时而导致的错误,还是其他原因导致的Error对象了。
更多推荐
所有评论(0)