JS 异步解决方案的发展历程以及优缺点。
- 1、回调函数(callback)
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队
等着,会拖延整个程序的执行。)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return - 2、Promise
优点:解决了回调地狱的问题
缺点:无法取消 Promise(return 一个pendding状态的promise即可结果不向后传递) ,错误需要通过回调函数来捕获 - 3、Generator
特点:可以控制函数的执行,可以配合 co 函数库使用 - 4、Async/await
优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使
用 await 会导致性能上的降低。
- 手写promise相关代码
// Promise 三种状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED"; // resolve
const REJECTED = "REJECTED"; // reject
const resolvePromise = (promise2, x, resolve, reject) => {
// 处理x的类型来决定下次then的状态是resolve还是reject
// promise2 === x的情况就相当于
// new Promise(resolve => resolve(1)).then(res => a1);
if (promise2 === x) {
return reject(
new TypeError(`Chaining cycle detected for promise #<Promise>`)
);
}
let called = false;
// 判断x是不是一个普通函数
if ((typeof x === "object" && x !== null) || typeof x === "function") {
// 判断是否有then方法来判断是不是promise
try {
let then = x.then;
if (typeof then === "function") {
// 是promise情况 使用then.call来执行x.then方法是为了避免有的对象写的只能获取一次。
// x.then需要再获取一次 而then.call是上次的缓存
then.call(
x,
(y) => {
// 参数可能还是promise需要递归
// .then(res => new Promise(resolve => resolve(new Promise..)))
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return; // 防止多次调用
called = true;
reject(r);
}
);
} else {
// [1,2,3] {a:1}
resolve(x);
}
} catch (err) {
if (called) return; // 防止多次调用
called = true;
reject(err);
}
} else {
// 不是对象或者函数 普通值
resolve(x);
}
};
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
// 保存promise状态
this.status = PENDING;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
// 如果resolve或者reject一个promise
if (value instanceof Promise) {
return value.then(resolve, reject);
}
//规定: 如果一旦状态改变为成功或失败,就不能再变
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
// 使用throw new Error也可以进入rejected逻辑
reject(error);
}
}
then(onFulfilled, onRejected) {
// // 如果成功回调不是函数则将value继续向下传递,失败则将reason继续向后抛
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (val) => val;
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
// 返回一个新的promise。实现链式调用
// 因为then中的回调函数是异步执行的。为了确保newPromise存在,需要setTimeout
let newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
if (this.status === FULFILLED) {
// 如果执行then中报错,需要直接reject
try {
// 执行下then中的方法的返回值当成下一个then的参数传递
let x = onFulfilled(this.value);
// 返回值有多种情况。普通值或者还是一个promise
resolvePromise(newPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
}
if (this.status === REJECTED) {
// 如果失败的then中有报错或者还返回一个promise直接向后传递即可
let x = onRejected(this.reason);
reject(x);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(newPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason);
reject(x);
});
}
});
});
return newPromise;
}
}
/*
1. Promise.any vs Promise.all
Promise.any() 和 Promise.all() 从返回结果来看,它们 彼此相反 :
Promise.all() :任意一个 promise 被 reject ,就会立即被 reject ,并且 reject 的是第一个抛出的错误信息,只有所有的 promise 都 resolve 时才会 resolve 所有的结果
Promise.any() :任意一个 promise 被 resolve ,就会立即被 resolve ,并且 resolve 的是第一个正确结果,只有所有的 promise 都 reject 时才会 reject 所有的失败信息
另外,它们又有不同的 重点 :
Promise.all() 对所有实现都感兴趣。相反的情况(至少一个拒绝)导致拒绝。
Promise.any() 对第一个实现感兴趣。相反的情况(所有拒绝)导致拒绝。
2. Promise.any vs Promise.race
Promise.any() 和 Promise.race() 的 关注点 不一样:
Promise.any() :关注于 Promise 是否已经解决
Promise.race() :主要关注 Promise 是否已经解决,无论它是被解决还是被拒绝
*/
// 手写 使用了Promise.resolve(promise) 包裹promise,可能用户输入的是非promise,比如222, thenable: {then: ...}
// Promise.all;
Promise.all = function(promises) {
let successNum = 0;
let promisesLength = promises.length;
let arr = Array(promisesLength).fill(); //[undefined]
return new Promise((resolve, reject) => {
for (let index = 0; index < promisesLength; index++) {
const promise = promises[index];
Promise.resolve(promise).then((res) => {
arr[index] = res;
if(++successNum === promisesLength) resolve(arr)
}, (err) => {
reject(err)
})
}
})
}
// 从最快的服务器检索资源
// 来自世界各地的用户访问网站,如果你有多台服务器,则尽量使用响应速度最快的服务器,
// 在这种情况下,可以使用 Promise.any() 方法从最快的服务器接收响应
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
promises = Array.isArray(promises) ? promises : [];
let len = promises.length;
// 用于收集所有 reject
let errs = [];
// 如果传入的是一个空数组,那么就直接返回 AggregateError
if (len === 0)
return reject(new AggregateError("All promises were rejected"));
promises.forEach((promise) => {
Promise.resolve(promise).then(
(value) => {
resolve(value);
},
(err) => {
len--;
errs.push(err);
if (len === 0) {
reject(new AggregateError(errs));
}
}
);
});
});
};
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
for (let index = 0; index < promises.length; index++) {
const promise = array[index];
Promise.resolve(promise).then(res => {
resolve(res)
}, (err) => {
reject(err)
})
}
})
}
// promise.race可以很多改变状态的操作,比如取消promise。
// 或者像如下 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
Promise.prototype.finally = function(fn) {
let P = this.constructor;
return this.then(
value => P.resolve(fn()).then(() => value),
reason => P.resolve(fn()).then(() => { throw reason })
);
}
// 实现一个sleep函数
function sleep(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
})
}
2.有8个图片资源的url,已经存储在数组urls中,且已有一个函数function loading,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject
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://www.kkkk1000.com/images/wxQrCode2.png'];
// 异步加载图片,预加载能获取图片相关属性和数据
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
console.log('一张图片加载完成');
resolve();
}
img.onerror = reject;
img.src = url;
})
};
function limitLoad(urls, handler, limit) {
// 对数组做一个拷贝
const sequence = [...urls];
let promises = [];//并发请求到最大数
promises = sequence.splice(0, limit).map((url, index) => {
// 这里返回的 index 是任务在 promises 的脚标,用于在 Promise.race 之后找到完成的任务脚标
return handler(url).then(() => {
return index;
});
});
// 利用数组的 reduce 方法来以队列的形式执行
return sequence.reduce((last, url, currentIndex) => {
return last.then(() => {
// 返回最快改变状态的 Promise
return Promise.race(promises)
}).catch(err => {
// 这里的 catch 不仅用来捕获前面 then 方法抛出的错误
// 更重要的是防止中断整个链式调用
console.error(err)
}).then((res) => {
// 用新的 Promise 替换掉最快改变状态的 Promise
promises[res] = handler(sequence[currentIndex]).then(() => {
return res
});
})
}, Promise.resolve()).then(() => {
return Promise.all(promises)
})
}
limitLoad(urls, loadImg, 3);
- eventLoop(浏览器 & node)
网友评论