API usage and implementation
The constructor
- The Promise constructor accepts as an argument a function that takes two arguments
- Resolve A method to change a promise from a pending state to a fulfilled state
- Reject Method for changing a promise from its pending state to its Rejected state
- Function arguments passed to the Promise constructor are executed as soon as the Promise constructor is invoked
- Once the status of a Promise changes, it cannot be changed
let p = new Promise((resolve, reject) = > {
console.log(1)
setTimeout(() = > {
console.log(6)
resolve(2)},0)
console.log(3)
reject(4)
console.log(5)})Copy the code
The result is as follows:
1
3
5
// error: Uncaught (in promise) 4
6
Copy the code
Promise.prototype.then
- The then method returns a new Promise instance
- The then method takes two functions as arguments, andThese two parameters are optional
- One is the callback after the promise becomes a depressing state, and the parameters of the callback are the values passed in when the promise resolve is fulfilled
- One is the callback after the Promise changes to rejected state. The parameter of the callback is the reject or error passed in when the Promise reject is rejected
- If neither argument is passed, the new Promise object returned by then will accept the final state of the original Promise that called the THEN as its final state
- The THEN method supports chained callbacks, which essentially return a new Promise instance
- You can call THEN multiple times on the same Promise instance, and the end result is that all the callbacks from then are placed in a queue and called one by one as the state changes
let p1 = new Promise((resolve) = > {
console.log('start')
resolve(1)
})
.then((res) = > {
console.log('> > >' + res)
return 2
})
.then((res) = > {
console.log('= = =' + res)
return Promise.reject(res)
})
.then(
(res) = > {
console.log(The '-' + res)
},
(reason) = > {
console.log('reject:' + reason)
}
)
// Call then several times
p1.then(() = > {
console.log('p1_2')
})
p1.then(() = > {
console.log('p1_3')})console.log('end')
Copy the code
The result is as follows:
// start
// end
/ / > > > 1
/ / = = = 2
// reject: 2
// p1_2
// p1_3
Copy the code
- The then method is called synchronously and, depending on the state, puts the callback passed to it into the microtask queue or into its own callback array
- Why is it that
p1_2
和p1_3
Is it printed at the end? (Note the change of reference in P1)
Promise.prototype.catch
- The essence of the catch method is that the then method only provides failed callbacks
- This method also returns a new promise, because it is essentially the then method
let p = new Promise((resolve, reject) = > {
reject('error 1')
})
.catch((e) = > {
console.log('catch 1:' + e)
})
.catch((e) = > {
console.log('catch 2:' + e)
throw 'error 2'
})
// The nature of the catch method
.then(undefined.(e) = > {
console.log(e)
})
Copy the code
The result is as follows:
'error 1'
'error 2'
Copy the code
The principle of the catch
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
Copy the code
Promise.prototype.finally
- A finally method is a method that will be called no matter what the final state of a Promise instance is
- This avoids the need to write the same statement once in both then() and catch().
- The finally method also returns a value that returns a Promise object with the finally callback set
- There is no way to know the final state of the promise, so no arguments are accepted in the finally callback function
let p = new Promise((resolve, reject) = > {
setTimeout(() = > resolve(1), 1000)
})
.finally(() = > {
console.log('finally')
})
.then((res) = > {
console.log(res)
})
Copy the code
The following output is displayed:
finally
1
Copy the code
Finally, the principle of
Promise.prototype.finally = function (fn) {
// The then method on this must be called because passthrough is required
return this.then(
(value) = > {
// put it in the resolve method because there may be asynchronous operations in fn
return Promise.resolve(fn()).then(() = > {
/ / passthrough the value
return value
})
},
(err) = > {
return Promise.resolve(fn()).then(() = > {
// Abnormal penetration
throw err
})
}
)
}
Copy the code
Promise.resolve
- The resolve method returns aA given valueThe parsed Promise object
- If the value is a PROMISE, the promise is returned
- If the value is thenable (that is, with the “then” method), the returned promise will “follow “the thenable object and adopt its final state
- Otherwise the returned promise will be fulfilled with this value
// The value is the case of a Promise instance, which will be returned directly
let p1 = Promise.resolve(
new Promise((resolve) = > {
resolve(1)
})
)
p1.then((res) = > {
console.log(res) / / 1
})
// The value is the case of the thenable object
let p2 = Promise.resolve({
then: function (onFulfill, onReject) {
onFulfill('fulfilled! ')
},
})
p2.then((res) = > {
console.log(res) // fulfilled
})
// Values are other values
let p3 = Promise.resolve(100)
p3.then((res) = > {
console.log(res) / / 100
})
Copy the code
Resolve principle
Promise.resolve = function (data) {
// If it is an instance of Promise, return it directly
if (data instanceof Promise) return data
return new Promise((resolve, reject) = > {
// If it is a Thenable object, the returned promise state will follow the thenable object state
if (data.then && typeof data.then === 'function') {
return data.then(resolve, reject)
}
// In other cases, return directly
resolve(data)
})
}
Copy the code
Promise.reject
- The static reject method returns a Promise object with a reason for the rejection
Reject the principle
Promise.reject = function (reason) {
return new Promise((_, reject) = > reject(reason))
}
Copy the code
Promise.all
- The method returns a promise instance that is resolved only if all of the promise instances passed in are resolved, and returns the results of each promise in turn in the results array
- Once a promise is rejected, the method returns a reject promise and the reject of the first rejected promise
let genPromise = function (data, reason) {
return new Promise((resolve, reject) = > {
return reason ? reject(reason) : resolve(data)
})
}
let p1 = genPromise(1)
let p2 = genPromise(2)
let p3 = genPromise(3)
Promise.all([p1, p2, p3]).then((res) = > {
console.log(res) / / [1, 2, 3]
})
let p4 = genPromise(undefined.4)
let p5 = genPromise(5)
let p6 = genPromise(undefined.6)
Promise.all([p4, p5, p6]).then(undefined.(reason) = > {
console.log(reason) / / 4
})
Copy the code
All the principle
Promise.all = function (promises) {
return new Promise((resolve, reject) = > {
if (!Array.isArray(arr))
return reject(new TypeError('argument must be an Array'))
if(! arr.length)return resolve([])
let results = []
let len = promises.length
for (let i = 0; i < len; i++) {
promises[i].then(
(value) = > {
// You cannot push directly because you cannot guarantee the order in which each promise will return
// Error: results.push(value)
results[i] = value
// Then resolve
if(! --len) resolve(results) },(reason) = > {
reject(reason)
}
)
}
})
}
Copy the code
Promise.any
- It is currently Stage4
- Receive a Promise iterable
- If one promise succeeds, the successful promise is returned, without waiting for the others to complete
- If none of the iterables succeeds, a failed promise and an instance of the AggregateError type are returned, which is a subclass of Error used to group single errors together
let genPromise = function (data, reason) {
return new Promise((resolve, reject) = > {
return reason ? reject(reason) : resolve(data)
})
}
// As long as there is a success, return that success
let p1 = genPromise(1)
let p2 = genPromise(undefined.2)
let p3 = genPromise(3)
Promise.any([p1, p2, p3]).then((res) = > {
console.log(res) / / 1
})
// If all fails, return a failed promise
let p4 = genPromise(undefined.4)
let p5 = genPromise(undefined.5)
let p6 = genPromise(undefined.6)
Promise.any([p4, p5, p6]).then(undefined.(reason) = > {
console.log(reason) // 'AggregateError: All promises were rejected'
})
Copy the code
Any principle
- If the argument passed in is an empty iterable, a Promise with the already Rejected state is returned.
- If the parameters passed in do not contain any promises, an Asynchronous completed Promise is returned.
Promise.any = function (promises) {
return new Promise((resolve, reject) = > {
if (!Array.isArray(arr))
return reject(new TypeError('argument must be an Array'))
if(! arr.length)return reject(new AggregateError('All promises were rejected'))
let len = promises.len
for (let p of promises) {
p.then(
(value) = > {
resolve(value)
},
(reject) = > {
if(! --len)return reject(new AggregateError('All promises were rejected'))})}})}Copy the code
Promise.race
- Returns a promise that is resolved or rejected once a promise in the iterator is resolved or rejected
let genPromise = function (data, reason) {
return new Promise((resolve, reject) = > {
return reason ? reject(reason) : resolve(data)
})
}
// Once a promise is resolved or rejected, the returned promise is resolved or rejected
let p1 = genPromise(1)
let p2 = genPromise(undefined.2)
let p3 = genPromise(3)
Promise.race([p1, p2, p3]).then((res) = > {
console.log(res) / / 1
})
// Once a promise is resolved or rejected, the returned promise is resolved or rejected
let p4 = genPromise(undefined.4)
let p5 = genPromise(5)
let p6 = genPromise(undefined.6)
Promise.race([p4, p5, p6]).then(undefined.(reason) = > {
console.log(reason) / / 4
})
Copy the code
Principle of race
Promise.race = function (promises) {
return new Promise((resolve, reject) = > {
if (!Array.isArray(promises)) return reject(promises + 'must be an array')
for (let p of promises) {
p.then(resolve, reject)
}
})
}
Copy the code
Promise.allSettled
- Return a promise after all the given promises have fulfilled or Rejected, with an array of objects, each representing the corresponding promise result
- It is commonly used when multiple asynchronous tasks that do not depend on each other have successfully completed
let genPromise = function (data, reason) {
return new Promise((resolve, reject) = > {
return reason ? reject(reason) : resolve(data)
})
}
// Wait for all promises to become completed and return the array of results
let p1 = genPromise(1)
let p2 = genPromise(undefined.2)
let p3 = genPromise(3)
Promise.allSettled([p1, p2, p3]).then((res) = > {
console.log(res)
/** * 0: {state: 'fulfilled', value: 1} * 1: {state: 'rejected', reason: 2} * 2: {state: 'fulfilled', value: 3} * */
})
Copy the code
AllSettled principle
Promise.allSettled = function (promises) {
if (!Array.isArray(promises)) return reject(promises + 'must be an array')
let res = []
let len = promises.length
for (let i = 0; i < len; i++) {
promises[i].then(
(data) = > {
res[i] = {
state: 'fulfilled'.value: data,
}
},
(reason) = > {
res[i] = {
state: 'rejected',
reason,
}
}
)
}
}
Copy the code
Write a Promise
Most of the above APIS are already implemented, but now take A look at the implementation of the Promise itself, referring to the Promise A+ specification
The first step
- Promise instance has three status: Pending | Rejected | Fulfilled
- Takes a function as an argument that is called immediately upon instantiation
- Function arguments take two arguments: resolve & Reject, which are two methods
- When the resolve method is called, the state of the Promise changes from Pending to depressing
- When reject is called, the state of the Promise changes from Pending to Rejected
class Promise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(excutor) {
// This is a big pity. // This is used to record the values to be passed when the Promise becomes fulfilled
this.value = void 0
// This is used to record the rejection passed when a Promise changes to Rejected
this.reason = void 0
// The initial state of the Promise instance
this.state = Promise.PENDING
// The method used to change a promise from a pending state to a fulfilled state
let resolve = (data) = > {
if (this.state === Promise.PENDING) {
this.data = data
this.state = Promise.FULFILLED
}
}
// The method used to change the promise state from pending to Rejected
let reject = (reason) = > {
if (this.state === Promise.PENDING) {
this.reason = reason
this.state = Promise.REJECTED
}
}
Function arguments are executed as soon as the constructor is called
try {
excutor(resolve, reject)
} catch (e) {
reject(e)
}
}
}
Copy the code
Ok, step one is done
The second step
Then method
- The then method takes two functions as arguments
- Both parameters are optional and not required
- OnFulFilled function, which accepts a value as the return value of the promised resolved state
- The onRejected function accepts a Reason as a reason for the state rejected by a Promise
- Both functions are called asynchronously
- The return value of the THEN method is a new Promise instance that cannot be the same as the Promise instance that called the THEN method
- This allows the THEN method to be called chained
- The THEN method can be called multiple times on a Promise instance
- The registered callbacks are invoked in sequence when the state of the Promise instance changes
class Promise {
constructor() {
/ /... Omit the other
this.onFulfilledCb = []
this.onRejectedCb = []
/ /... Omit the other
}
then(onFulfilled, onRejected) {
// These two arguments must be function types, if not for bottom handling
onFulfilled =
typeofonFulfilled ! = ='function' ? (value) = > value : onFulfilled
onRejected =
typeofonRejected ! = ='function'
? (reason) = > {
throw reason
}
: onRejected
The then method returns a new promise
let p2 = new Promise((resolve, reject) = > {
// onFulfilled and onRejected can only be called if the implementation environment stack contains only platform code, which is uniformly wrapped and catches the exception
let wrappedOnFulfilledCb = () = > {
queueMicrotask(() = > {
try {
let x = onFulfilled(this.value)
// According to the standard, if the ondepressing method returns a value, the following Promise resolution process needs to be run
resolePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
let wrappedOnRejectedCb = () = > {
queueMicrotask(() = > {
try {
let x = onRejected(this.reason)
// According to the standard, if the onRejected method returns a value, then the following Promise resolution procedure also needs to be run
resolePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// If the state is pending, the callback is stored temporarily until the state changes
if (this.state === Promise.PENDING) {
this.onFulfilledCb.push(wrappedOnFulfilledCb)
this.onRejectedCb.push(wrappedOnRejectedCb)
}
// This is a big pity
if (this.state === Promise.FULFILLED) {
wrappedOnFulfilledCb()
}
// If it is the Rejected state
if (this.state === Promise.REJECTED) {
wrappedOnRejectedCb()
}
})
return p2
}
}
Copy the code
Therefore, the THEN method itself is called synchronously, and the two callbacks passed to it are placed on the microtask queue to be executed asynchronously
Catch method
class Promise {
/ /... Omit the other
catch(fn) {
return this.then(undefined, fn)
}
}
Copy the code
The third step
Promise resolution process
The process is a lot of steps, but it’s easy to understand, just follow the standard step by step
- If x and p2 are the same object, a TypeError is returned
This is mainly to avoid this situation:
let p1 = new Promise((resolve) = > {
resolve(1)})let p2 = p1.then((value) = > {
return p2
})
// TypeError: Chaining cycle detected for promise #<Promise>
Copy the code
- If x is not an object or function, execute the promise with x as an argument
- If x is an object (including a Promise object), or a function
- Assign x. teng to then
- If an error e is thrown when taking the value x. teng, reject the promise based on e
- If then is a function, call it with x as the function’s scope this, passing two callback functions as arguments
- The first parameter is called resolvePromise and the second parameter is called rejectPromise
- If resolvePromise is called with the value y, run [[Resolve]](promise, y)
- If rejectPromise is invoked with argument r, reject the promise with argument r
- If both resolvePromise and rejectPromise are invoked, or if the same parameter is invoked more than once, the first call is preferred and the remaining calls are ignored
- If calling the then method throws an exception e
- If a resolvePromise or rejectPromise has already been invoked, it is ignored
- Otherwise, reject the promise based on e
- If then is not a function, execute the promise with an x argument
implementation
function resolvePromise(p2, x, resolve, reject) {
if (p2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>'))}if ((typeof x === 'object' || typeof x === 'function') && x ! = =null) {
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
(y) = > {
if (called) return
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) = > {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
Copy the code
The complete code
class Promise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(excutor) {
// This is a big pity. // This is used to record the values to be passed when the Promise becomes fulfilled
this.value = void 0
// This is used to record the rejection passed when a Promise changes to Rejected
this.reason = void 0
// The initial state of the Promise instance
this.state = Promise.PENDING
// Resolve the scenario where the then method is called multiple times
this.onFulfilledCb = []
this.onRejectedCb = []
// Use the arrow function to bind this to
let resolve = (value) = > {
if (this.state === Promise.PENDING) {
this.value = value
this.state = Promise.FULFILLED
this.onFulfilledCb.forEach((cb) = > cb())
}
}
// Use the arrow function to bind this to
let reject = (reason) = > {
if (this.state === Promise.PENDING) {
this.reason = reason
this.state = Promise.REJECTED
this.onRejectedCb.forEach((cb) = > cb())
}
}
Function arguments are executed as soon as the constructor is called
try {
excutor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// These two arguments must be function types, if not for bottom handling
onFulfilled =
typeofonFulfilled ! = ='function' ? (value) = > value : onFulfilled
onRejected =
typeofonRejected ! = ='function'
? (reason) = > {
throw reason
}
: onRejected
The then method returns a new promise
let p2 = new Promise((resolve, reject) = > {
// onFulfilled and onRejected can only be called if the implementation environment stack contains only platform code, which is uniformly wrapped and catches the exception
let wrappedOnFulfilledCb = () = > {
queueMicrotask(() = > {
try {
let x = onFulfilled(this.value)
// According to the standard, if the ondepressing method returns a value, the following Promise resolution process needs to be run
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
let wrappedOnRejectedCb = () = > {
queueMicrotask(() = > {
try {
let x = onRejected(this.reason)
// According to the standard, if the onRejected method returns a value, then the following Promise resolution procedure also needs to be run
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// If the state is pending, the callback is stored temporarily until the state changes
if (this.state === Promise.PENDING) {
this.onFulfilledCb.push(wrappedOnFulfilledCb)
this.onRejectedCb.push(wrappedOnRejectedCb)
}
// This is a big pity
if (this.state === Promise.FULFILLED) {
wrappedOnFulfilledCb()
}
// If it is the Rejected state
if (this.state === Promise.REJECTED) {
wrappedOnRejectedCb()
}
})
return p2
}
catch(fn) {
return this.then(null, fn)
}
static resolve(data) {
if (data instanceof Promise) return data
return new Promise((resolve, reject) = > {
try {
if (data.then && typeof data.then === 'function') {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
})
}
static reject(reason) {
return new Promise((_, reject) = > {
reject(reason)
})
}
}
function resolvePromise(p2, x, resolve, reject) {
// The whole process is implemented step by step according to the standard
if (p2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>'))}if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
(y) = > {
if (called) return
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) = > {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
// ======== run test cases using ========= //
// Promise.deferred = function () {
// let dfd = {}
// dfd.promise = new Promise((resolve, reject) => {
// dfd.resolve = resolve
// dfd.reject = reject
/ /})
// return dfd
// }
// module.exports = Promise
Copy the code
The test results
Testing tools
Q&A
The output sequence
The first one
const first = () = >
new Promise((resolve, reject) = > {
console.log(3)
let p = new Promise((resolve, reject) = > {
console.log(7)
setTimeout(() = > {
console.log(5)
resolve(6)},0)
resolve(1)
})
resolve(2)
p.then((arg) = > {
console.log(arg)
})
})
first().then((arg) = > {
console.log(arg)
})
console.log(4)
Copy the code
The following output is displayed:
3
7
4
1
2
5
Copy the code
- First is called, the Promise constructor executes, and outputs first
3
- The second Promise constructor inside first executes and outputs
7
- When setTimeout is encountered, it is placed in the Tasks queue, and the main thread continues down
- Resolve (1) Will change the state of P to depressing
- Resolve (2) Will change the state of external promise to depressing
- P.teng registers the first microtask, and the first internal logic is executed
- The first().then method registers the second microtask
- The output
4
, the main thread is empty, and the microtask queue is checked - The two promise states are now fulfilled. First, the micro-task registered by P. Teng will be called and output
1
- Then call the first().then registered microtask, output
2
. The microtask queue is empty, and the Tasks queue is checked instead - Timer is out of time, take out and execute, output
5
- Resolve (6) is ignored because the promise state is already completed
The second
Although this problem seems simple, the operation mechanism basically involves all the flow of Promise
let p1 = new Promise((resolve) = > {
resolve(1)
})
.then((v) = > {
console.log(v)
return v + 1
})
.then((v) = > {
console.log(v)
return v + 1
})
.then((v) = > {
console.log(v)
return v + 1
})
let p10 = new Promise((resolve) = > {
resolve(10)
})
.then((v) = > {
console.log(v)
return v + 1
})
.then((v) = > {
console.log(v)
return v + 1
})
.then((v) = > {
console.log(v)
return v + 1
})
Copy the code
The following output is displayed:
1
10
2
11
3
12
Copy the code
Alternate output. Can you explain why?
- The P1 constructor will execute, and P1 will immediately resolve, which becomes depressing
- The call to p1.then puts the registered callback into the microtask queue; The then method executes and returns a new promise, which we’ll call P2 (and so on, p3, p4)
- The then methods of P2, P3, and P4 are also executed (because the THEN methods are called synchronously), but p2, P3, and P4 are still pending, so their callbacks are stored in their respective callback arrays instead of in the microtask queue. You can look at this in combination with the code
let wrappedOnFulfilledCb = () = > {
queueMicrotask(() = > {
try {
let x = onFulfilled(this.value)
// According to the standard, if the ondepressing method returns a value, the following Promise resolution process needs to be run
resolvePromise(p2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// If the state is pending, the callback is stored temporarily until the state changes
if (this.state === Promise.PENDING) {
this.onFulfilledCb.push(wrappedOnFulfilledCb)
}
Copy the code
As you can see, in pending state, each maintained callback array stores a wrap callback, and the time to push the real user’s callback to the microtask queue is after the wrap callback is executed
Where does the wrap callback get executed? In the case of the wrappedonserver ledcb callback, there are two times it can be called
// when the THEN method is called and the state of the Promise is not pending
if (this.state === Promise.FULFILLED) {
wrappedOnFulfilledCb()
}
// In this case, when resolve is called, the user's callback is actually put on the microtask queue
let resolve = (value) = > {
if (this.state === Promise.PENDING) {
this.value = value
this.state = Promise.FULFILLED
// The callback performed here is simply a callback to the wrap (wrappedoncallback)
// What it does is put the user's actual callback into the microtask queue
this.onFulfilledCb.forEach((cb) = > cb())
}
}
Copy the code
Ok, continue to
- The process of P10 is the same as above
- After the main thread task is executed, check the microtask queue. At this time, there are only P1 and P10 in the microtask queue. Take out their registered callbacks and execute them, and output 1 and 10 respectively
- During the callback execution of P1 and P10, the state of P2 and P11 will be fulfilled, and their THEN callback will be put into the micro-task team
- This step is done during the resolvePromise process when the Resolve method is called
- Then repeat the above steps until all the microtasks are empty
So the result of the final execution is output alternately
Output 1,2,3 every second
Use the promise implementation to print 1,2,3 every second
; [1.2.3].reduce((p, x) = > {
return p.then((res) = > {
return new Promise((resolve) = > {
setTimeout(() = > {
console.log(x)
resolve()
}, 1000)})})},Promise.resolve())
Copy the code
The traffic lights flickered alternately
Red light 3 seconds on once, yellow light 2 seconds on once, green light 1 second on once; How to keep three lights alternating and circulating
Three lighting functions:
function red() {
console.log('red')}function green() {
console.log('green')}function yellow() {
console.log('yellow')}Copy the code
implementation
- Use recursion to achieve repeated effects
let sleep = (time, fn) = >
new Promise((resolve) = > {
setTimeout(() = > {
fn()
resolve()
}, time * 1000)})let step = () = > {
return Promise.resolve()
.then(() = > {
return sleep(3, red)
})
.then(() = > {
return sleep(2, green)
})
.then(() = > {
return sleep(1, yellow)
})
.then(() = > {
step() // Use recursion to achieve repeated effects
})
}
step()
Copy the code
Implement asynchronous concurrency control
/** * asynchronous concurrency limit **@export
* @param {Array} sources
* @param {*} callback
* @param {*} limit
* @returns* /
async function limitAsyncConcurrency(sources, callback, limit = 5) {
let done
let lock = []
let results = []
let runningCount = 0
let total = sources.length
if(! total)return
const p = new Promise((resolve) = > (done = resolve))
const block = async() = > {return new Promise((resolve) = > lock.push(resolve))
}
/ / remove the lock
const next = () = > {
const fn = lock.shift()
fn && fn()
runningCount--
}
const excutor = async (index, item) => {
// Limit concurrency
if (runningCount >= limit) await block()
runningCount++
new Promise((resolve, reject) = >
callback(index, item, resolve, reject)
).then((res) = > {
total--
next()
results[index] = res
if(! total) { done(results) } }) }for (const [index, item] of sources.entries()) {
excutor(index, item)
}
return p
}
let sources = ['1.text'.'2.txt'.'3.txt'.'4.txt'.'5.txt'.'6.txt'.'7.txt']
let getFile = (index, file, resolve, reject) = > {
setTimeout(() = > {
console.log(index, file)
resolve(file)
}, 1000)
}
imitAsyncConcurrency(sources, getFile, 3).then((res) = > {
console.log(res)
})
Copy the code
The following output is displayed:
0 '1.text'
1 '2.txt'
2 '3.txt'
/ / interval of 1 s
3 '4.txt'
4 '5.txt'
5 '6.txt'
/ / interval of 1 s
6 '7.txt'
// Finally output
['1.text'.'2.txt'.'3.txt'.'4.txt'.'5.txt'.'6.txt'.'7.txt']
Copy the code