异步发展历程
我们知道Promise的出现是为了处理js中的异步操作。在Promise出现之前,多用回调来处理异步操作。一旦异步操作稍微复杂起来,就很容易出现传说中的回调地狱,会导致代码的可读性和可维护性降低。 例如我们要实现以下异步操作,用回调的执行方式如下:
let fs = require('fs');
fs.readFile('a.txt', (err,data) => {
fs.readFile('b.txt', (err,data) => {
fs.readFile('c.txt', (err,data) => {
//...
});
});
});
复制代码
而用Promise则可以这样写:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
readFile('a.txt')
.then(data => {
return readFile('b.txt');
}).then(data => {
return readFile('c.txt');
}).then(data => {
//...
});
复制代码
当然我们也可以用ES7中的async/await去写,会更简便:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
async function read() {
await readFile('a.txt');
await readFile('b.txt');
await readFile('c.txt');
// ...
}
read()
复制代码
通过对比,可以发现async/await的写法是最简洁的,基本上趋近于同步代码的写法了。不过async/await也是在promise的基础上演化的,可以看作是基于promise的语法糖。所以接下来我们就来看看如何手写一个promise.
Promise代码基本用法
let p = new Promise((resolve, reject) => {
resolve(123)
})
p.then((data) => {
console.log(data) // 此处可以取到resolve中的传入的参数值
}, (err) => {
console.log('e', err)
})
复制代码
通过new Promise()传入一个executor参数
class Promise {
constructor(executor) {
this.status = 'pending' // 默认状态是等待态
this.value = undefined
this.reason = undefined
let resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'resolved'
}
}
let reject = reason => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejecte'
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// 会取promise的返回结果作为外层下一次then的参数
// 如果返回
// then方法调用后,返回的是一个新的promise
then(onFufilled, onRjected) {
if (this.status === 'resolved') {
onFufilled(this.value)
}
if (this.status === 'rejected') {
onRjected(this.reason)
}
}
}
复制代码
以上就实现了具有最基本功能的promise啦,但这还远远不够哦,因为这时还不支持在promise中执行异步操作。如:
let p = new Promise((resolve, reject) => {
// promise中的异步操作
setTimeout(() => {
resolve(123)
}, 1000)
})
p.then((data) => {
console.log(data) // 此时取不到resolve中的参数值
}, (err) => {
console.log('e', err)
})
复制代码
所以当promise是pending态时,我们可以在then方法调用时先将onFulfilled和onRjected保存起来,当状态转换为resolved态或rejected时调用。这里用到了发布订阅的思想。
constructor(executor) {
// ...
this.onResolvedCallbacks = [] // 存放成功的回调 发布订阅
this.onRejectedCallbacks = [] // 存放失败的回调
let resolve = value => {
if (this.status === 'pending') {
// ...
this.onResolvedCallbacks.forEach(fn => fn()) // 发布
}
}
let reject = reason => {
if (this.status === 'pending') {
// ...
this.onRejectedCallbacks.forEach(fn => fn())
}
}
// .
}
then(onFufilled, onRjected) {
// ...
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => { // 订阅
onFufilled(this.value)
})
this.onRejectedCallbacks.push(() => { // 订阅
onRjected(this.reason)
})
}
}
}
复制代码
至此我们的promise已经支持异步代码了。接下来我们需要实现then方法的链式调用,这里是比较难和绕的部分。then方法有如下特点:
- then方法每次都会返回一个新的promise,如若显式返回一个promise,就将此promise作为其返回值(不能是当前调用then方法的promise),如若返回一个普通值,则需手动生成一个新的promise并将其return
- then方法是异步调用的
- 若then方法中出现异常,则调用失败态方法(reject)跳转到下一个then的onRejected
- 当then()中未传回调参数时可以看作跳过当前then方法继续向下个then方法执行,即值的穿透
function resolvePromise(promise2, x, resolve, reject) {
// 判断x是不是promise
if (promise2 === x) { // x为promise2,不能自己等待自己完成
return reject(new TypeError('循环引用'))
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // x是个promise
let called // 防止resolve后调用reject
try {
let then = x.then // 取x的then方法
if (typeof then === 'function') { // 如果then是函数就认为是promise
then.call(x, y => { // 如果y是promise就继续递归解析promise
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, err => {
if (called) return
called = true
reject(err)
})
}
} catch(e) {
if (called) return
called = true
reject(e)
}
} else { // then是一个普通对象
resolve(x)
}
}
then(onFufilled, onRjected) {
// 解决不传onFufilled onRjected的问题 , 值的穿透
onFufilled = typeof onFufilled === 'function' ? onFufilled : y => y
onRjected = typeof onRjected === 'function' ? onRjected : err => { throw err }
let promise2 // then需要返回一个新的promise
if (this.status === 'resolved') {
promise2 = new Promise((resolve, reject) => {
// x为promise则取其作为promise2的成功结果, 如果x为一个普通值,则作为promise2成功的结果
console.log(promise2)
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)
})
//console.log(promise2)
// resolvePromise(promise2, x, resolve, reject)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRjected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)
})
}
if (this.status === 'pending') { // 主要解决executor里异步代码
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => { // 订阅
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFufilled(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
})
}
return promise2
}
复制代码
resolvePromise
用来解析then方法的返回值
p.then(data => {
return new Promise((resolve,reject)=>{
//resolve传入的还是Promise
resolve(new Promise((resolve,reject)=>{
resolve(2);
}));
});
})
复制代码