Javascript 异步编程(五)
Promise
Promise 承诺,在异步编程中用来描述异步任务,当前不执行,承诺执行,美剧里经常是I
promise~ 挂在嘴边
ES6 Promise Api
-
new Promise(..) 构造器
var p = new Promise( function(resolve,reject){ // 接收函数作为构造器参数 // resolve(..)用于将promise状态从pending改为fulfilled // reject(..)用于将promise状态从pending改为rejected } );
resolve(..) 既可能完成 promise,也可能拒绝,要根据传入参数而定。
如果传给 resolve(..) 的是一个非 Promise、非 thenable 的立即值,这个 promise 就会用这个值完成。
如果传给 resolve(..) 的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的)promise 将取用其最终决议值或状态。
-
Promise.resolve 创建一个已完成的 Promise
语法上两者是等同的
var p1 = new Promise( function(resolve,reject){ resolve( "Oops" ); } ); var p2 = Promise.resolve( "Oops" );
-
Promise.reject 创建一个已被拒绝的 Promise
var p1 = new Promise( function(resolve,reject){ reject( "Oops" ); } ); var p2 = Promise.reject( "Oops" );
- Promise.all
输入一组promise,返回一个新的promise。只有传入的全部 promise 的状态都变为
fulfilled
,返回 promise 才能改变为fulfilled
。可以理解为逻辑与(&&
) - Promise.allSettled
输入一组promise,返回一个新的promise。只有传入的所有 promise 的状态都改变后(
fulfilled
或rejected
),返回 promise 才能改变为fulfilled - Promise.race
输入一组promise,返回一个新的promise。结果的promise与第一个 状态发生改变(
fulfilled
或rejected
)promise相同 - Promise.any
接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成
fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。类似于逻辑或 (||
)
若向 Promise.all([ .. ]) 传入空数组,它会立即完成,但 Promise.race([ .. ]) 会挂住,且永远不会完成。
实例方法:
- then()
then(..) 接受一个或两个参数:第一个用于完成回调,第二个用于拒绝回调。如果两者中
的任何一个被省略或者作为非函数值传入的话,就会替换为相应的默认回调。默认完成回
调只是把消息传递下去,而默认拒绝回调则只是重新抛出(传播)其接收到的出错原因 - catch()
只接受一个拒绝回调作为参数,并自动替换默认完成回调。换句话说,它等价于 then(null,..):
- finally()
接收一个参数,不论promise的最终状态如何都会执行,
then(..) 和 catch(..) 也会创建并返回一个新的 promise,这个 promise 可以用于实现
Promise 链式流程控制。
如果完成或拒绝回调中抛出异常,返回的 promise 是被拒绝的。如果任意一个回调返回非 Promise、非 thenable 的立即值,这个值会被用作返回 promise 的完成值。
如果完成处理函数返回一个 promise 或 thenable,那么这个值会被展开,并作为返回promise 的决议值。
异常控制
对于错误处理,一般我们使用try…catch,但是它只能捕捉同步代码的异常。
try{
setTimeout(function () {
console.log('happen',x)
})
}catch (e) {
console.log('exception',e)
}
// Uncaught ReferenceError: x is not defined
在promise中,如果发生异常,将由then()中的reject处理函数进行错误捕获。
Promise.reject(10).then((val)=>{
console.log(val)
},(err)=>{
console.log('then',err)
})
// then 10
Promise.resolve(10).then(function (val) {
console.log(val)
return Promise.reject(val)
}).then(val => {
console.log(val)
}, err => {
console.log('then', err)
})
// 10
// then 10
如果忘记在then()中对异常进行捕获,将会丢失被忽略和抛弃的Promise错误。于是,实现Promise异常控制的最佳实践就是以catch()结尾来进行异常处理。
Promise.resolve(10).then(function (val) {
console.log(val)
return Promise.reject(val)
}).then(val => {
console.log(val)
}).catch(err=>{
console.log('err',err)
})
// err 10
但是,如果我们在catch中发生了异常呢?
Promise.reject(10).catch(err=>{
console.log('catch',10+x)
})
// ReferenceError: x is not defined
Promise.reject(10).catch(err => {
console.log('catch', 10 + x)
}).catch(err=>{
console.log('catch2',err)
})
// catch2 ReferenceError: x is not defined
其实我们可以在调用链后面再次补充一个catch来保证捕获任何可能出现的错误。但是这样的写法不是很优雅,有种常用的方法是使用done(标志着promise的结尾,不会创建和返回promise,不在es6规范中),现阶段可以通过增加 polyfill
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (err) {
// 抛出一个全局错误
setTimeout(() => { throw err }, 0);
});
};
在浏览器中,我们可以使用 unhandledrejection
事件来捕获这类 error:
window.addEventListener('unhandledrejection', function(event) {
// 这个事件对象有两个特殊的属性:
alert(event.promise); // [object Promise] - 生成该全局 error 的 promise
alert(event.reason); // Error: Whoops! - 未处理的 error 对象
});
new Promise(function() {
throw new Error("Whoops!");
});
如果出现了一个 error,并且在这儿没有 .catch
,那么 unhandledrejection
处理程序(handler)就会被触发,并获取具有 error 相关信息的 event
对象,所以我们就能做一些后续处理了
Promise 实践
以下代码的执行顺序是?
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
- 构造器
new Promise
是同步执行的,所以先打印1,2 - then的回调属于微任务,异步执行
- 同步执行
console.log(4)
,接着打印 4 - 开始执行微任务队列,
console.log(3)
,打印出3
第二段代码
const promise=Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);//1
过程分解
- Promise.resolve(1) 生成的promise ,返回值为1,状态为fulfilled
- then(2)中,传入了非函数,返回的promise 的值为上一步的promise的值(即为1),状态为fulfilled
- then(Promise.resolve(3)) 这一步中参数类型为promise,仍取上一步的promise的值(即为1),状态为fulfilled
- then(console.log), 这一步参数为函数,传入值1
最佳实践
- 不要忘记catch捕获
- then方法中使用return
- 传递函数给then方法
- 不要把promise写成嵌套
手动实现Promise
class Promise {
constructor (executor) {
// 参数校检
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
this.initValue()
this.initBind()
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
initValue () {
// state:pending/fulfilled/rejected
this.value = null//终值
this.reason = null//拒因
this.state = Promise.PENDING
//用于处理异步
this.onFulfilledCallbacks = [] //成功回调
this.onRejectedCallbacks = []//失败回调
}
resolve (value) {
//成功后的一系列操作(状态的改变,成功回调的执行)
//状态不可逆 从pending开始
if (this.state === Promise.PENDING) {
this.state = Promise.FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(this.value))
}
}
//绑定this
initBind () {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
reject (reason) {
//失败后的一系列操作(状态的改变,失败回调的执行)
if (this.state === Promise.PENDING) {
this.state = Promise.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
catch (onRejected) {
return this.then(null, onRejected) // 相当于then里的成功回调只传个null
}
then (onFulfilled, onRejected) {
//实现链式调用且改变了后面then方法的值,必须通过新的实例
let promise2 = new Promise((resolve, reject) => {
// 参数校检
if (typeof onFulfilled !== 'function') {
onFulfilled = function (value) {
return value
}
}
if (typeof onRejected !== 'function') {
onRejected = function (reason) {
throw reason
}
}
if (this.state === Promise.FULFILLED) {
//模拟异步任务
setTimeout(() => {
try {
const x = onFulfilled(this.value)
Promise.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.state === Promise.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
Promise.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// 处理异步
if (this.state === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
const x = onFulfilled(value)
Promise.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(reason => {
setTimeout(() => {
try {
const x = onRejected(reason)
Promise.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
Promise.resolvePromise = function (promise, x, resolve, reject) {
// x与promise相等时 循环引用
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 函数调用一次的开关项 防止重复调用
let called
if (x instanceof Promise) {
/**
* x为 Promise
* 1. x 为 pending,promise需保持 pending 直至x被拒绝或执行
* 2. x 为 fulfilled 用相同的值执行promise
* 3. x 为 rejected 用相同的拒因执行promise
*/
x.then(value => {Promise.resolvePromise(promise, value, resolve, reject)},
reason => {reject(reason)})
} else if (x && (typeof x === 'object' || typeof x === 'function')) {
// x 为对象或函数
try {
let then = x.then
// 处理thenable
if (typeof then === 'function') {
then.call(x,
value => {
if (called) return
called = true
Promise.resolvePromise(promise, value, resolve, reject)
},
reason => {
if (called) return
called = true
reject(reason)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
Promise.resolve = function (val) {
return new Promise(resolve => {
resolve(val)
})
}
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
resolve(data)
})
}
})
}
Promise.all = function (promises) {
let result = []
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(value => {
result.push(value)
if (result.length === promises.length) {
resolve(arr)
}
}, reject)
}
})
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。