这里是对我理解的 Promose 的一个总结,如有不恰当的地方,希望指出。
说到 promise 就要知道为啥会出现 promise, promise 到底解决了一个什么问题 (学习一门新技术,最好知道它是怎么诞生的,以及解决了什么问题 :)
简单描述一下:
Promise 解决了什么问题
Promise 解决的是异步编码风格的问题。页面上的任务都是执行在主线程之上的,对于页面来说,主线程就是它的整个世界。在执行一项耗时任务的时候(网络文件下载,获取设备摄像头等设备信息任务),这些任务会被放到页面主线程之外的进程或者线程中去执行。 -- 页面编程的一大特点:异步回调
Web 页面的单线程架构决定了异步回调,而异步回调影响到了我们的编码方式。
Promise 没出现前
在没有 Promise 之前,假设有个下载请求,需要封装异步代码,让处理流程变得线性, 参考下边的伪代码
XFetch(Request('https://abc.com'),
function resolve(res) {
console.log(res)
XFetch(Request('https://abc.com/aPage'),
function resolve(res) {
console.log(res)
XFetch(Request('https://abc.com/bPage')
function resolve(res) {
console.log(res)
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
可以从上面代码看到嵌套了好几个回调,每个回调都有不确定性。一旦需求变得复杂起来,就会产生问题
- 任务的不确定性,需要对每个任务的执行结果做两次判断
- 嵌套多个回调,陷入了回调地狱,代码的可读性也变差了,不方便维护。
- 多次错误处理
Promise 出现后
先了解下 Promise 的 不可变性:在《你不知道的 JavaScript》一书中说到:“Promise 决议后就是外部不可变的值,我们可以安全的把这个值传递给第三方,并确信它不会被有意无意地修改。” 那么这个不可变性是 Promise 中最重要和最基础的因素。
Promise 出现后到底解决了什么问题呢?
- 信任问题。使用回调函数时可能会出现以下问题(不确定性)
- 调用回调过早
- 调用回调过晚 (或不被调用)
- 调用回调次数过少或过多
那么使用了 Promise 之后我们可以明确的知道对 then(...) 的第一个参数来说,毫无疑义,总是处理完成的情况。
- 消灭了嵌套调用,实现了线性调用 (使用过 promise 的小伙伴应该都知道这让人愉快的使用方式)
- 每次你对 Promise 调用 then(...), 它都会创建并返回一个新的 Promise, 我们可以将其链接起来。
- 不管从 then(...) 调用完成的回调(第一个参数)返回的值是什么,它都会被自动设置为被链接 Promise 的完成
- 合并了多个任务的错误处理。
结合下边的代码理解
function test(resolve, reject) {
let rand = Math.random();
if (rand > 0.5) resolve()
else reject()
}
let p1 = new Promise(test);
let p2 = p1.then((value) => {
return new Promise(test)
})
let p3 = p2.then((value) => {
return new Promise(test)
})
p3.catch((error) => {
console.log("error")
})
上面代码有 3 个 promise 对象, p1 - p3 无论哪个任务抛出了异常,都可以通过最后一个对象 p3.catch 来捕获异常, 这样就解决了每个任务都需要单独处理异常的问题。
以上是对 Promise 一个总结。tip:如果想很详细的了解 Promise,推荐看看《你不知道的 JaveScript》中卷。
实现 promise
先来看看平时我们是怎么使用 promise 的
let p = new Promise((resolve, reject) => {
let rand = Math.random();
if (rand > 0.5) {
resolve('喵喵喵')
}
else reject('error')
})
p.then((res) => {
console.log(res) // 喵喵喵
})
p.catch((error)) => {
console.log(error) // error
})
分析一下结构:
- myPromise 为一个 构造函数,this 指向 实例
- 三个 Status: Pending, Resolved, Rejected
- 参数:一个回调函数 (resolve, reject) => {...}
- 两个内部函数 resolve(), reject(), 用来决议 status,透穿 value
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
function myPromise(fn) {
let that = this
this.status = PENDING
this.value = null
this.resolvedCb = [] // 异步状态下 收集 resove cb
this.rejectedCb = [] // 异步状态下 收集 reject cb
function resolve(val) {
// 避免隐式转换,使用 that
if (that.status === PENDING) {
that.status = RESOLVED
that.value = val
// 执行一遍 cb
that.resolvedCb.forEach(cb => cb(that.value))
} // 确定 status 值
}
function reject(error) {
if (that.status === PENDING) {
that.status = REJECTED
that.value = error
that.rejectedCb.forEach(cb => cb(that.value))
}
}
try {
fn(resolve, reject)
} catch(e) {
reject(e)
}
}
实现 myPromise.then(...)
myPromsie.then(onFulfilled, onRejected) 接收两个函数,一个代表完成状态,一个代表 reject 状态。
myPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
// 透传值,待会我们测一下透传效果
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : v => v;
onRejected = typeof onRejected === "function" ? onRejected: r => { throw r; };
// 异步状态下收集 cb
if (that.status === PENDING) {
that.resolvedCb.push(onFulfilled);
that.rejectedCb.push(onRejected);
}
if (that.status === RESOLVED) {
onFulfilled(that.value);
}
if (that.status === REJECTED) {
onRejected(that.value);
}
return this;
};
// 测试
let p = new myPromise((resolve, reject) => {
let rand = Math.random();
if (rand > 0.5) {
resolve('success')
}
else reject('error')
})
p.then((res) => {
console.log(res)
},
(error) => {
console.log(error)
})
// 异步状态下的测试
let p = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 3000)
}).then((res) => {
console.log(res) // 3 秒后打印 success
})
// 测试透传值
let m = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve("可爱的我出现了~");
}, 1000);
})
.then()
.then()
.then(res => console.log(res)); // 可爱的我出现了
实现 myPromise.all(...)
关于promise.all, MDN 上描述是:“Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。”
综上得出:
- promise.all 接收一个 promises 数组
- promise.all 返回一个 promise 实例
- promise.all(promises) 是返回当所有 promises 都 resolve 后的 list
- 其中有一个 promise 失败了,返回 reject 代码实现
myPromise.prototyep.all = function(promises) {
return new myPromise((resolve, reject) => {
let res = [];
let count = 0;
// 注意 for 里边用 let 定义 i,闭包问题
for (let i = 0; i < promises.length; i++) {
promises[i].then(value => {
count++;
res[i] = value;
if (count === promises.length) {
resolve(res);
}
}, error => {
reject('error')
});
}
});
};
// 测试
let p1 = new myPromise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
});
let p2 = new myPromise(resolve => {
setTimeout(() => {
resolve(2);
}, 500);
});
let p3 = new myPromise(resolve => {
setTimeout(() => {
resolve(3);
}, 300);
});
myPromise.all([p1, p2, p3]).then(res => console.log(res)); // [1,2,3]
实现 allSettled
MDN 中描述: “该Promise.allSettled()方法返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。”
与 promise.all() 对比
promise.all(promises) 是 promises 中有一个promise reject,那么整个promise.all都返回 reject,当我们做数据处理的时候,有时候想更多的展示页面内容,promise.all 可能不是我们的首选。
在 ES2020 中,有了 Promise.allSettled,它很好的解决了 promise.all 的痛点,如定义是返回了所有的 promises list,无论它是 reslove 还是 reject
(tip: 使用时注意浏览器的兼容性)
结合代码看一下
Promise.allSettled([
Promise.reject({ code: 500, msg: '服务异常' }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
]).then(res => {
console.log(res)
/*
0: {status: "rejected", reason: {…}}
1: {status: "fulfilled", value: {…}}
2: {status: "fulfilled", value: {…}}
*/
// 过滤掉 rejected 状态,尽可能多的保证页面区域数据渲染
RenderContent(
res.filter(el => {
return el.status !== 'rejected'
})
)
})
接下来结合一个图片 list 加载实现一下,其实也可以当作不兼容 promise.allSettled 浏览器的 polyfill
var urls = ['https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 'https://www.kkkk1000.com/images/getImgData/gray.gif', 'https://www.kkkk1000.com/images/getImgData/Particle.gif', 'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/10/29/166be40ccc434be0~tplv-t2oaga2asx-image.image'];
function allSettled(iterable) {
return new Promise((resolve, reject) => {
function addElementToResult(i, elem) {
result[i] = elem;
elementCount++;
if (elementCount === result.length) {
resolve(result);
}
}
let index = 0;
重写包装一下返回结果,加入返回状态
for (const promise of iterable) {
const currentIndex = index;
promise.then(
(value) => addElementToResult(
currentIndex, {
status: 'fulfilled',
value
}),
(reason) => addElementToResult(
currentIndex, {
status: 'rejected',
reason
}));
index++;
}
if (index === 0) {
resolve([]);
return;
}
let elementCount = 0;
const result = new Array(index);
});
}
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
console.log('一张图片加载完成');
resolve(url);
}
img.onerror = reject
img.src = url
})
};
let arr = urls.map(url => loadImg(url))
allSettled(arr).then(res => {
console.log("resolve", res)
})
实现 race
MDN 中描述 :“Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。”
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
综上:一旦 promises list 中有值 resolve 就会输出(谁快谁输出)
const myRace = function (promises) {
if (!Array.isArray(arr)) {
throw new Error('promises not a Array')
}
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((value) => {
resolve(value);
return;
}, //哪个 then 先捕捉到,那个先返回。
(err) => {
reject(err)
return;
}
)
}
})
}
// 测试
let p1 = new myPromise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
});
let p2 = new myPromise(resolve => {
setTimeout(() => {
resolve(2);
}, 500);
});
let p3 = new myPromise(resolve => {
setTimeout(() => {
resolve(3);
}, 300);
});
myRace([p1, p2, p3]).then(res => console.log(res)); // 3
差点忘记的 catch
一口气写了这么多,差点忘记实现 catch 了,道理都懂,那就直接上代码好了,顺便结合一下之前写的 promise
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
function myPromise(fn) {
let that = this
this.status = PENDING
this.value = null
this.resolvedCb = [] // 异步状态下 收集 resove cb
this.rejectedCb = [] // 异步状态下 收集 reject cb
function resolve(val) {
// 避免隐式转换,使用 that
if (that.status === PENDING) {
that.status = RESOLVED
that.value = val
// 执行一遍 cb
that.resolvedCb.forEach(cb => cb(that.value))
} // 确定 status 值
}
function reject(error) {
if (that.status === PENDING) {
that.status = REJECTED
that.value = error
that.rejectedCb.forEach(cb => cb(that.value))
}
}
try {
fn(resolve, reject)
} catch(e) {
reject(e)
}
}
// .then
myPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
// 透传值,待会我们测一下透传效果
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : v => v;
onRejected = typeof onRejected === "function" ? onRejected: r => { throw r; };
// 异步状态下收集 cb
if (that.status === PENDING) {
that.resolvedCb.push(onFulfilled);
that.rejectedCb.push(onRejected);
}
if (that.status === RESOLVED) {
onFulfilled(that.value);
}
if (that.status === REJECTED) {
onRejected(that.value);
}
return this;
};
// catch
myPromise.prototype.catch = function(errFn) {
errFn =
typeof errFn === "function"
? errFn
: r => {
throw r;
};
const that = this;
if (that.status === PENDING) {
this.rejectedCb.push(errFn);
}
if (that.status === REJECTED) {
errFn(that.value);
}
return this;
};
let p = new myPromise((resolve, reject) => {
reject('错了吧')
})
p.catch((err => {
console.log('知道', err)
}))
总结
这里就不写怎么实现 promise A+ 了,累 0.0
好了,终于写完了,不管怎样,学一个东西都要了解一下它的原理。希望大家看完之后能够帮到你们,然后实现一个自己的 promise 怎么样 :)