✊不积跬步,无以至千里;不积小流,无以成江海。
Promise介绍
Promise 是 JavaScript 中用于处理异步操作的一种机制。它是 ECMAScript 6 (ES6) 引入的一种对象,用于管理异步操作的状态和结果。Promise 可以更优雅地处理异步代码,并提供了一种可靠的方式来处理成功、失败和异步操作完成后的结果。
Promise 对象有三种状态:
- Pending(等待中): 初始状态,表示操作正在进行中。
- Resolve(已完成): 表示异步操作已成功完成。
- Rejecte(已拒绝): 表示异步操作失败或被拒绝。
一个 Promise 对象的状态一旦改变,就不会再改变。
使用 Promise 的基本语法如下:
const myPromise = new Promise((resolve, reject) => {
// 异步操作
// 如果操作成功,调用 resolve(value)
// 如果操作失败,调用 reject(error)
});
myPromise
.then((value) => {
// 处理操作成功的结果
})
.catch((error) => {
// 处理操作失败的结果
});
在上述代码中,我们创建了一个 Promise 对象 myPromise
,并传入一个执行器函数(executor function)。执行器函数接收两个参数 resolve
和 reject
,分别是用于将 Promise 状态改变为 Fulfilled 和 Rejected 的函数。
在执行器函数中,我们执行异步操作,并根据操作的结果调用 resolve
或 reject
。如果操作成功,我们调用 resolve
函数,并传入成功的结果(value
);如果操作失败,我们调用 reject
函数,并传入失败的原因(error
)。
使用 .then()
方法,我们可以在 Promise 对象状态变为 Fulfilled 时处理操作成功的结果。通过 .catch()
方法,我们可以在 Promise 对象状态变为 Rejected 时处理操作失败的结果。
Promise 还提供了其他方法,如 .finally()
、.all()
、.race()
等,用于处理多个 Promise 对象的组合和并行操作。
使用 Promise 可以更好地管理异步代码,避免回调地狱(callback hell)的问题,使代码更加清晰、可读和可维护。它已成为现代 JavaScript 开发中处理异步操作的标准方式。
Promise API
Promise API 提供了一些方法来处理 Promise 对象的状态和结果。下面是一些常用的 Promise API:
Promise.resolve(value)
: 返回一个状态为 Fulfilled 的 Promise 对象,并将给定的value
作为成功的结果。Promise.reject(reason)
: 返回一个状态为 Rejected 的 Promise 对象,并将给定的reason
作为失败的原因。Promise.all(iterable)
: 接收一个可迭代对象(如数组)作为参数,返回一个新的 Promise 对象。该 Promise 对象在所有的 Promise 对象都变为 Fulfilled 状态时变为 Fulfilled,并将所有 Promise 对象的结果作为一个数组传递。Promise.race(iterable)
: 接收一个可迭代对象作为参数,返回一个新的 Promise 对象。该 Promise 对象在第一个变为 Fulfilled 或 Rejected 状态的 Promise 对象完成时变为相同的状态,并将第一个完成的 Promise 对象的结果传递。Promise.allSettled(iterable)
: 接收一个可迭代对象作为参数,返回一个新的 Promise 对象。该 Promise 对象在所有的 Promise 对象都变为 Fulfilled 或 Rejected 状态时变为 Fulfilled,并返回包含每个 Promise 对象状态和结果的对象数组。Promise.prototype.then(onFulfilled, onRejected)
: 在 Promise 对象的状态变为 Fulfilled 时调用onFulfilled
回调函数,在状态变为 Rejected 时调用onRejected
回调函数。该方法返回一个新的 Promise 对象,可以用于链式调用。Promise.prototype.catch(onRejected)
: 在 Promise 对象的状态变为 Rejected 时调用onRejected
回调函数。该方法返回一个新的 Promise 对象,用于处理 Promise 对象的错误。Promise.prototype.finally(onFinally)
: 在 Promise 对象的状态变为 Fulfilled 或 Rejected 时,无论如何都会调用onFinally
回调函数。该方法返回一个新的 Promise 对象,用于添加最终处理。
这些是 Promise API 的一些常见方法,用于处理 Promise 对象的状态、结果和错误。通过使用这些方法,我们可以更灵活地处理异步操作和多个 Promise 对象的组合。
用promise封装一个延时器
使用 Promise 封装 setTimeout 可以让 setTimeout 支持 Promise 的异步操作。
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000).then(() => console.log('1 秒后输出'));
这个示例中,我们创建了一个名为 delay 的函数,它接收一个毫秒数,返回一个 Promise 对象。在 delay 函数内部,我们使用 setTimeout 函数来等待指定的毫秒数,然后通过 Promise 的 resolve 函数来返回一个成功状态的 Promise。
在调用 delay 函数时,我们使用 then 方法来指定当 Promise 对象变为成功状态时要执行的操作,即输出 '1 秒后输出'。在 1 秒后,delay 函数内部的 setTimeout 函数会调用 resolve 函数,将 Promise 对象的状态改为成功,并触发 then 方法中指定的操作。
通过这种方式,我们可以使用 Promise 封装任何需要异步等待的操作,让它们支持 Promise 的异步操作
手写promise.all
见链接:
面试复习题-手写promise.all - 掘金 (juejin.cn)
手写promise.race
方法返回一个Promise实例,一旦迭代器中的某个 promise 完成(resolved)或失败(rejected),返回的 promise 就会 resolve 或 reject
Promise2.race = function(arrP) {
let hasValue = false
let hasError = false
return new Promise2((resolve, reject) => {
for(let i = 0; i < arrP.length; i++) {
arrP[i].then(data => {
!hasValue && !hasError && resolve(data)
hasValue = true
}, error => {
!hasValue && !hasError &&reject(error)
hasError = true
})
}
})
}
实现细节
1. 函数定义:
Promise2.race = function(arrP) {
// ...
}
race
函数是一个静态方法,定义在 Promise2
类上,这意味着可以直接使用 Promise2.race
调用该函数,而不需要创建 Promise2
的实例。
2. 参数:
Promise2.race = function(arrP) {
// ...
}
race
函数接受一个参数 arrP
,该参数是一个包含 Promise2
对象的数组。
3. 标记和初始化:
Promise2.race = function(arrP) {
let hasValue = false
let hasError = false
// ...
}
函数定义了两个标记: hasValue
和 hasError
。它们用来追踪 arrP
数组中的 promise 是否已经 resolved 或 rejected。这两个标记都初始化为 false
。
4. 创建新的 Promise:
Promise2.race = function(arrP) {
let hasValue = false
let hasError = false
return new Promise2((resolve, reject) => {
// ...
})
}
函数使用 new Promise2
构造函数创建一个新的 Promise2
对象。构造函数接受一个执行器函数,该函数有两个参数: resolve
和 reject
。这两个参数用来 resolved 或 rejected 新创建的 promise。
5. 循环遍历 promises:
Promise2.race = function(arrP) {
let hasValue = false
let hasError = false
return new Promise2((resolve, reject) => {
for(let i = 0; i < arrP.length; i++) {
arrP[i].then(data => {
// ...
}, error => {
// ...
})
}
})
}
代码使用 for
循环遍历 arrP
数组。对于数组中的每个 promise (arrP[i]
),它使用 then
方法。
6. 处理 resolved 的 promise:
...
arrP[i].then(data => {
!hasValue && !hasError && resolve(data)
hasValue = true
}, error => {
// ...
})
}
})
}
.then
方法接受两个回调函数: 一个用于成功 resolved (data
),另一个用于 rejected (error
)。
成功 resolved 的回调函数检查两个条件,使用逻辑 AND 运算符 (&&):
!hasValue
: 该条件确保第一个 resolved 的 promise 决定race的结果。如果hasValue
为true
,则意味着已经有一个 promise resolved,并且race结束。因此,即使其他 promise 之后也 resolved,也不会影响最终结果。!hasError
: 该条件确保race只在第一次 resolved 时终止,而不是在 rejected 之后。如果hasError
为true
,则意味着已经有一个 promise rejected,并且race已经结束。即使其他 promise 之后也 resolved,也不会影响最终结果。
如果两个条件都为 true,则表示第一个 resolved 的 promise 还没有被其他 resolved 或 rejected 的 promise 影响,因此可以将其 resolved 数据作为最终结果。此时,resolve
函数会被调用,并传入 resolved 的数据 (data
)。
7. 处理 rejected 的 promise:
...
arrP[i].then(data => {
// ...
}, error => {
!hasValue && !hasError &&reject(error)
hasError = true
})
}
})
}
rejected 的回调函数也检查相同的两个条件,使用逻辑 AND 运算符 (&&)。如果两个条件都为 true,则 reject
函数会被调用,并使用 rejected promise 的错误 (error
) 作为参数。然后将 hasError
标记设置为 true
。
手写promise.allSettled
//参数:一个iterable对象。比如 String, Array, TypedArray, Map, and Set
//返回一个Promise对象,会异步resolve,结果是一个数组,里面每一项是个对象,包含 status 和 value 或者reason两个属性。每个对象是iterable对象每一个 resolve或者reject的结果构成的对象
//如果iterable对象为空,则返回一个已经被resolve的Promise对象,resolve内容为空数组
Promise.allSettled = function(iterable) {
return new Promise((resolve, reject) => {
let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
if(promises.length === 0) return resolve([])
let values = []
let count = 0
for(let i=0; i<promises.length; i++) {
promises[i].then(v => {
values[i] = { status: 'fulfilled', value: v }
}, reason => {
values[i] = { status: 'rejected', reason: reason }
}).finally(() => {
count++
if(count === promises.length) {
resolve(values)
}
})
}
})
}
//测试
Promise.allSettled([3, Promise.resolve(4), 5])
.then(v => console.log(v))
console.log(Promise.allSettled(''))
let p1 = new Promise(r => setTimeout(r, 100, 1))
let p2 = new Promise(r => setTimeout(r, 200, 2))
Promise.allSettled([Promise.reject(4), p1, p2])
.then(v => console.log(v))
Promise并发控制
代码只有十几行,但实现的非常巧妙。用到了async、await
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; //2
const executing = []; //3
for (const item of array) { //4
const p = Promise.resolve().then(() => iteratorFn(item)); //5
ret.push(p); //6
if (poolLimit <= array.length) { //7
const e = p.then(() => executing.splice(executing.indexOf(e), 1)); //8
executing.push(e); //9
if (executing.length >= poolLimit) { //10
await Promise.race(executing); //11
}
}
}
return Promise.all(ret); //15
}
代码虽然不多,但需要对Promise非常熟悉才能理解,下面就模拟代码执行跑一遍执行过程。
假设 poolLimit = 3, array是一个长度为10的url列表, iteratorFn是一个返回Promose对象的函数用于发送请求,模拟一下执行过程:
- line2:创建数组ret,用于存放全部的Promise对象
- line3:创建数组execting,用于存放并发限制的处于Pending状态的Promise对象
- line4:item是array的第一项
- line5: iteratorFn(item) 得到一个pending状态的Promise对象 p。(这里之所以不直接 p = iteratorFn(item),是为了兼容iteratorFn是同步函数的场景,保证返回的p一定是Promose对象,见下方测试代码)
- line6:p放入ret
- line7:如果限制数量poolLimit 小于等于 数组的总长度再执行限制。当前poolLimit=3,arr.length=10,进入if逻辑
- line8:根据刚刚的p创建一个Promise对象e,等p resolve的时候才执行then里的回调,把e从executing数组移除(PS:目前e还没放入数组,在line9会放进去)
- line9:把e放入executing
- line10:目前executing长度小于poolLimit限制长度3,不进入if,回到line4执行下一次循环
- .... 循环执行到第3次时,到达line10,此时ret数组为[p1_pending, p2_pending, p3_pending],executing数组为[e1_pending, e2_pending, e3_pending],其中p1_pending 的resolve会触发e1的移出和resolve(第8行then里的箭头函数执行完e就resovle)
- line11:卡住,等待executing 数组里的[e1,e2,e3]看哪个最快resolve 。假设p2最先resolve,p2的resolve触发e2的resolve,当e2 resolve 之后,Promise.race(executing)得到结果, 此刻回到line4,for循环才进入下一轮,execting数组里为[e1_pending, e3_pending],ret数组为[p1pending, p2_fulfilled, p3_pending]。
- line4:... ,继续下一轮循环,execting数组始终保持最多不超过3个
- ...
- line15:当for循环结束之后,ret数组里包含全部arr封装的promise对象,返回Promise.all(ret),得到新的Promise对象(等ret所有的全resolve后该Promise对象才resolve,同时得到所有数据)
以下是测试代码:
const curl = (i) => {
console.log('开始' + i);
return new Promise((resolve) => setTimeout(() => {
resolve(i);
console.log('结束' + i);
}, 1000+Math.random()*1000));
};
/*
const curl = (i) => {
console.log('开始' + i);
return i;
};
*/
let urls = Array(10).fill(0).map((v,i) => i);
(async () => {
const res = await asyncPool(3, urls, curl);
console.log(res);
})();
Promise的实现
Promise 是 JavaScript 的内置对象,它的实现涉及到一些复杂的异步操作和状态管理。下面是一个简化版的 Promise 实现,用于演示其基本原理:
class Promise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback(this.value));
}
};
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback(this.reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === 'pending') {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
const result = onFulfilled(value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const result = onRejected(reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
-
首先,我们定义了一个名为
Promise
的类,它代表了一个 Promise 对象。 -
在构造函数中,我们初始化了 Promise 对象的初始状态为
'pending'
,并创建了用于存储结果和回调函数的变量。 -
resolve
和reject
是两个内部函数,用于将 Promise 对象的状态从'pending'
改变为'fulfilled'
或'rejected'
。它们接受一个参数,分别是成功的结果value
和失败的原因reason
。当状态改变时,它们会触发相应的成功或失败的回调函数。 -
在构造函数中,我们调用了
executor
函数,并传入resolve
和reject
函数作为参数。executor
函数是在创建 Promise 对象时传入的函数,它接受这两个参数,并用于执行异步操作。我们用try-catch
块包裹executor
函数的执行,以处理可能的异常。 -
then
方法用于注册成功和失败的回调函数,并返回一个新的 Promise 对象。它接受两个参数:onFulfilled
回调函数和onRejected
回调函数,分别用于处理成功和失败的情况。在then
方法内部,我们根据 Promise 对象的当前状态执行相应的回调函数。- 如果状态为
'fulfilled'
,我们使用setTimeout
来模拟异步操作,并在下一个事件循环中执行onFulfilled
回调函数。如果回调函数执行成功,则调用新的 Promise 对象的resolve
方法,传递成功的结果。 - 如果状态为
'rejected'
,我们使用setTimeout
来模拟异步操作,并在下一个事件循环中执行onRejected
回调函数。如果回调函数执行成功,则调用新的 Promise 对象的resolve
方法,传递成功的结果。 - 如果状态为
'pending'
,表示 Promise 对象的状态还未确定,我们将onFulfilled
和onRejected
回调函数分别放入onFulfilledCallbacks
和onRejectedCallbacks
数组中,以便在 Promise 对象状态确定后执行。
- 如果状态为
-
catch
方法是then
方法的一种特殊形式,用于注册处理失败的回调函数。它实际上是调用then
方法的简写形式,将第一个参数设置为null
。
这个简化版的 Promise 实现提供了基本的 Promise 功能,包括状态管理、回调函数注册、链式调用等。然而,真正的 Promise 实现要更复杂,涉及到更多的细节和错误处理机制。这个示例仅用于说明 Promise 的基本原理,实际使用中应使用原生的 JavaScript Promise 对象或符合 Promise/A+ 规范的第三方库。