Last Commit: 2024-01-06 17:50:27
views:
Promise
Promise
is a powerful alternative way to replace callback in JS. Compared to callback, Promise
provides a better way to write coherent codes.
See its standard from Promise/A+.
How to use
First of all, let's see its TS definition:
/**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
The first function in then is resolve function, the second function is reject function which can on catch the error from reject
, not from throw
.
The functions resolve
and reject
is to change the status of Promise
.
There is a demo:
const resolveTask = () => new Promise((resolve, reject) => resolve(1))
.then(res => {
console.log('resolve', res);
throw Error('throw error')
}, err => {
console.log('reject', err);
})
.catch(err => console.log('catch', err));
const rejectTask = () => new Promise((resolve, reject) => reject(1))
.then(res => {
console.log('resolve', res);
throw Error('throw error')
}, err => {
console.log('reject', err);
})
.catch(err => console.log('catch', err));
// resolve 1
// catch Error: throw error
resolveTask();
// reject 1
rejectTask();
Promise implementation
For me, the key point to understand Promise's then and catch is the event-loop. Because the main thread task will be operated to the end first. So the thenList
and onCatch
must be ready when the first callback starts.
There is a simple example I wrote:
class MyPromise {
static statusList = {
pending: Symbol("pending"),
resolved: Symbol("resolved"),
rejected: Symbol("rejected"),
};
static resolve = (p) => {
if (p instanceof MyPromise) return p;
return new MyPromise((resolve) => resolve(p));
};
static reject = (p) => {
if (p instanceof MyPromise) return p;
return new MyPromise((_, reject) => reject(p));
};
status = MyPromise.statusList.pending;
thenList = [];
onCatch = undefined;
constructor(action) {
action(this.resolve.bind(this), this.reject?.bind?.(this));
}
then(cb) {
this.thenList.push(cb);
return this;
}
catch(cb) {
this.onCatch = cb;
return this;
}
resolve(value) {
queueMicrotask(() => {
const task = this.thenList.shift().bind(this);
if (task) {
MyPromise.resolve(task(value))
.then(this.resolve.bind(this))
.catch((e) => {
this.status = MyPromise.statusList.rejected;
if (this.onCatch) this.onCatch(e);
else throw e;
});
} else {
this.status = MyPromise.statusList.resolved;
}
});
return this;
}
reject(value) {
this.status = MyPromise.statusList.rejected;
if (this.onCatch) this.onCatch(value);
else throw new Error(value);
return this;
}
}
var task = () =>
new MyPromise((resolve) => {
setTimeout(() => {
resolve(1);
}, 1000);
}).then((v) => {
console.log(v);
return v + 1;
});
task().then((v) => console.log(v));
Promise.all
, Promise.race
, Promise.finally
and Promise.parallel
About all
and race
, I know a really vivid and interesting metaphor: The all
is a horse race that ends when all horses reach the terminal point, the race
is a horse race that ends when the first horse reaches the terminal point.
In other word, the all
will return results of all callback tasks, the race
will return the callback result of the first finished task.
Notice, race
and all
both will run the whole asynchronize task, the only different is the final returned value.
Their implements are as below:
const task = (delay: number, fail?: boolean) => new Promise((resolve, reject) => {
setTimeout(() => {
if (!fail) resolve(delay);
else reject(delay);
}, delay);
});
Promise.myRace = (tasks: Promise<unknown>[]) => new Promise((resolve, reject) => {
tasks.forEach(async task => {
try {
const res = await task;
// a Promise can be resolved once
resolve(res);
} catch (e) {
reject(e);
}
});
});
Promise.myAll = (tasks: Promise<unknown>[]) => new Promise((resolve, reject) => {
const res = [];
tasks.forEach(async task => {
try {
const data = await task;
res.push(data);
if (res.length === tasks.length) resolve(res);
} catch (e) {
reject(e);
}
});
});
Promise.prototype.myFinally = function(cb) {
return this.then(
v => Promise.resolve(cb()).then(() => v),
e => Promise.resolve(cb()).then(() => { throw e })
)
};
Promise.parallelByReduce = (tasks: Promise<unknown>[]) => new Promise(async (resolve, reject) => {
try {
resolve(await tasks.reduce(async (acc, crr) => {
acc = await acc;
acc.push(await crr);
return acc;
}, []));
} catch (e) {
reject(e);
}
});
Promise.parallelByRecursion = (tasks: Promise<unknown>[]) => new Promise(async (resolve, reject) => {
const res = [];
const iter = async () => {
if (tasks.length > 0) {
const task = tasks.shift();
const data = await task;
res.push(data);
iter();
}
};
try {
await iter();
resolve(res);
} catch (e) {
reject(e);
}
});
Promise.some = (tasks: Promise<unknown>[], count: number) => new Promise((resolve, reject) => {
const ret = [];
if (!count || !tasks.length) return resolve(ret);
const errors = [];
tasks.forEach(async task => {
try {
const data = await task;
if (ret.length + 1 === count) {
ret.push(data);
resolve(ret);
}
} catch (e) {
if (errors.length + 1 > tasks.length - count) {
errors.push(e);
reject(errors);
}
}
});
});
async & await
async
and await
it a better solution to replace then
. Writing asynchronize codes with synchronize sequence.
If you use JS bundler, like Babel, esbuild or swc, async
and await
will be transferred to Generator
, refer to this article.
With native async
and await
, async function
will return an AsyncFunction
object, which can be created as below (notice: it is not a global variable):
Object.getPrototypeOf(async function(){}).constructor