专业的编程技术博客社区

网站首页 > 博客文章 正文

如何停止使用回调和开始执行(停止调试快捷键)

baijin 2024-08-27 11:18:55 博客文章 4 ℃ 0 评论

在本文中,我们将介绍JavaScript以及如何在不使用任何第三方库的情况下编写结构良好,易于理解的异步代码并不困难。

JavaScript有两种处理异步任务的主要方法 - 回调和Promises。通常,Promises被认为比回调更容易使用和维护。但是,实际上,即使是Promises也不会让你开心。异步代码可能仍然很难阅读和理解。因此,像 co这样的第三方库提供了编写类似同步的异步代码的方法。

我个人更喜欢世界上的所有东西都像redux-saga一样清晰美丽。但并不是每个人都很幸运能够使用React和Redux来使用传奇。在本文中,我将展示在现代JavaScript中,不使用任何第三方库编写结构良好且易于理解的异步代码并不困难。

回调方法

让我们从一个例子开始吧。假设我们有一个可以从流中读取一些数据的对象,该对象使用事件发射器来通知每个对这些事件感兴趣的人。该事件是start, data,stop ,使事情有点复杂,pause。

因此,我们希望start 在收听事件时捕获我们想要 开始获取和存储数据的data 事件。在 stop 活动中我们需要执行一些数据处理。在 pause 事件中,我们停止等待下一个data 事件并等待start 继续获取和存储数据。

这是代码:

let data = '';

const handleStart = () => {

streamReader.removeAllListeners('pause', handlePause);

streamReader.on('data', (chunk, err) => {

if (err) {

console.error(err);

streamReader.removeAllListeners('data');

streamReader.removeAllListeners('pause');

return;

}

data += chunk;

})

}

const handleStop = () => {

streamReader.removeAllListeners('data');

streamReader.removeAllListeners('pause');

streamReader.removeAllListeners('stop');

processData(data, (err, result) => {

if (err) {

console.error(err);

return;

}

storeResult(result, () => {

console.log('Stored')

})

});

}

const handlePause = () => {

streamReader.removeAllListeners('data');

streamReader.on('start', handleStart);

}

streamReader.once('start', handleStart);

streamReader.on('stop', handleStop)

streamReader.on('pause', handlePause);

这里我们有一堆事件监听器和事件处理程序来实现上述流程。还有一些函数被调用processData , storeData 它们执行一些异步操作并在完成时调用回调。

这段代码有什么问题?嗯......我想,这是一场彻头彻尾的噩梦。首先,有一个全局变量 data,这是不可能摆脱的。另外,我在上面提到了一个流程,但代码中没有流程。很难理解动作的顺序,因此调试起来非常困难。人们并没有把它称为“回调地狱”。

出路

JavaScript中异步回调的好处在于,如果您不想使用它,则不必使用它们。任何回调都可能变成Promise。最简单的例子如下所示:

const processDataPromise = new Promise((resolve, reject) => {

processData(data, (err, result) => {

if (err) reject(err);

resolve(result);

});

})

或者更通用的解决方案:

function promisify(f, context, isEvent) {

const ctx = context || this;

return function () {

return new Promise((resolve, reject) => {

f.call(ctx, ...arguments, (...args) => {

const err = arguments ? args.find((a) => a instanceof Error) : null;

if (err) {

reject(err);

} else {

if (isEvent) {

resolve({

type: arguments[0],

cbArgs: [...args],

});

} else {

resolve([...args]);

}

}

})

});

}

}

一般的解决方案当然不会立即明确,所以让我解释一下。

该函数promisify 将异步函数作为第一个参数,并返回一个函数,该函数除了回调之外,采用与原始参数相同的所有参数。调用此返回函数时,它将返回Promise。原始函数在Promise内部调用,在原始函数调用回调时解析。如果原始函数具有上下文(context 参数promisify),则在Promise中调用时它将绑定到它。如果原始函数只是一个普通的异步函数,我们用回调的参数解析Promise。如果它是一个事件监听器(isEvent = true),我们返回事件类型和回调参数。如果回调被调用和错误,则Promise被拒绝。

应用程序promisify 看起来像这样:

const processDataPromise = promisify(processData);

const storeResultPromise = promisify(storeResult);

const onEventPromise = promisify(emitter.once, emitter, true);

Promise可以这样使用:

processDataPromise(data).then(([err, processedData]) => {

/* do something with the data*/

}

但还有更好的方法。

传奇般的天堂

我们确实需要一种更好的方法,因为将上述流程压缩到Promise链中几乎是不可能的。

更好的方法是JavaScript async 函数,这是同一流程的另一个实现:

async function readStream(streamReader, initialData) {

const processDataPromise = promisify(processData);

const storeResultPromise = promisify(storeResult);

const onEventPromise = promisify(emitter.once, emitter, true);

await onEventPromise('start');

let data = initialData || '';

while (true) {

try {

const event = await Promise.race([

onEventPromise('data'),

onEventPromise('stop'),

onEventPromise('pause'),

]);

const {type} = event;

if (type === 'data') {

const [chunk] = event.cbArgs;

data += chunk;

}

if (type === 'pause') {

func(streamReader, data);

break;

}

if (type === 'stop') {

const [err, processedData] = await processDataPromise(data);

await storeResultPromise(processedData);

return processedData;

}

} catch (err) {

handleError(err);

return;

}

}

}

嗯,这个实现有什么好处?首先,它看起来更漂亮。其次,更重要的是,此代码中有一个流程。它几乎就像流程图一样,您可以逐步跟踪每个循环和每个分支的整个序列。它看起来像我在开始时提到的一个传奇,但是没有必要知道有关redux-saga的任何事情来编写这样的代码。更重要的是,它是您可以使用的代码。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表