我保证这是 Async / Await 的实用指南
在 ES8 中,我们找到了另一种比回调更易读的异步代码编写方式,即 Async / Await。在 ES6 中,我们已经有了 Promises。要理解 Async / Await,首先必须理解 Promises。
承诺
const resolveAfter2Seconds = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 2000);
});
}
resolveAfter2Seconds()
.then(() => { console.log('resolved') }) // this gets executed
.catch(() => { console.log('some error') }); // this does not get executed
const rejectAfter2Seconds = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 2000);
});
}
rejectAfter2Seconds()
.then(() => { console.log('resolved') }) // this does not get executed
.catch(() => { console.log('some error') }); // this gets executed
resolveAfter2Seconds 函数将返回一个新的 Promise。每个 Promise 都有一个状态。初始状态为pending
。之后,它可以变为fulfilled
或rejected
。当状态为 时,fulfilled
它会将 resolve 中的值传递给 then 函数,然后你可以对其进行任何操作。如果状态变为rejected
then ,它将运行 catch() 函数。我希望现在你对 Promise 的基本概念已经清楚了。
问题
给出以下代码:
const resolveAfterXSeconds = (ms) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(ms);
}, ms);
});
}
resolveAfterXSeconds(2000)
.then((ms) => { console.log(`resolved after ${ms}`) });
resolveAfterXSeconds(1000)
.then((ms) => { console.log(`resolved after ${ms}`) });
这段代码大约会在 2 秒还是 4 秒内完成?什么时候我们会看到 console.log() ?那么这段代码是顺序执行、并发执行还是并行执行?
回答
这段代码是真正并行的。它会执行两个函数,然后返回第二个函数调用,因为超时只有 1000 毫秒;然后返回第一个函数调用,因为这里的超时是 2000 毫秒。所以你必须想想这是否真的是你想要的。也许这些函数调用相互依赖!所以这并非你真正想要的。
我所见过的一个可以实现这一目标的解决方案如下:
resolveAfterXSeconds(2000)
.then((ms) => {
console.log('promise in the first then');
resolveAfterXSeconds(1000).then((ms) => { console.log(`resolved after ${ms}`) })
return ms;
}).then((ms) => { console.log(`resolved after ${ms}`) });
我们首先以 2000 为参数调用函数,一旦解析完成,我们立即以 1000 为参数调用函数,然后返回第一个函数的毫秒数。返回值相当于 Promise.resolve(),这就是它在这里有效的原因。虽然这可以按顺序运行,但可读性不强,让我想起了我们想要避免的回调地狱。
但是 Promise.all() 怎么样?让我们看一个例子:
Promise.all([resolveAfterXSeconds(2000), resolveAfterXSeconds(1000)]).then((ms) => {
console.log(`resolved after ${ms[0]}`);
console.log(`resolved after ${ms[1]}`);
});
这段代码之所以是并发的,是因为 Promise.all() 会创建一个单独的 Promise,当它所依赖的所有 Promise 都解析完毕后,该 Promise 才会被解析。因此,两个 resolveAfterXSeconds 函数会被同时调用,但 then() 函数会在所有 Promise 都解析完毕后调用。之后,你会收到一个包含已解析 Promise 的数组。数组中每个解析值的顺序与 Promise 传递给 Promise.all() 函数的顺序相同。如果你有 2 个 API 调用,这种模式就很好用。例如,一个用于用户数据,一个用于位置信息,然后你可以将它们组合成一个对象。
我们需要所有这些信息来更好地理解 Async / Await!
异步/等待
让我们终于可以开始讨论 Async / Await 了!首先要说的是:Async / Await 并不能完全取代 Promises。Async / Await 通常更容易阅读,但也容易被误解。我们的第一个例子:
resolveAfterXSeconds = (ms) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(ms);
}, ms);
});
}
start = async () => {
const first = await resolveAfterXSeconds(2000);
console.log(first);
const second = await resolveAfterXSeconds(1000);
console.log(second);
}
start();
因此,我们仍在使用旧的 resolveAfterXSeconds 函数,这里没有任何变化。现在我们创建一个名为 start 的新函数,这里是箭头函数之前的 async 的第一个新东西。只有 async () => {} 才会返回一个函数。调用此函数将返回一个承诺。这里需要记住的重要一点是,如果承诺只是返回某些内容,它将立即实现。在下一行,我们也有一些新的东西。await 告诉 javascript 它必须在这里等待,直到右侧的承诺解决或拒绝,直到这个函数暂停。在我们的示例中,第一次调用 resolveAfterXSeconds 函数将花费 2 秒,然后它将运行 console.log,然后运行第二个 resolveAfterXSeconds 函数。因此,运行我们的启动函数大约需要 3 秒钟。最后,我们得到了我们想要的!在 javascript 中顺序运行的异步代码!
由此我们可以看出,Async/await 与 promise.then 是不一样的!这一点在编码时务必牢记。你必须使用正确的工具来完成正确的工作。
Async / Await 也可以像 promise.all 一样以并发方式使用。
resolveAfterXSeconds = (ms) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(ms);
}, ms);
});
}
concurrentStart = async () => {
const first = resolveAfterXSeconds(2000);
const second = resolveAfterXSeconds(1000);
console.log(await first);
console.log(await second);
}
唯一改变的是 await 现在在 console.log() 本身中。为什么现在是并发的?因为 first 和 second 都已经启动了,现在我们只是在等待它们完成,记住 async 会创建一个 Promise。如果你回想一下 Promise.all() 的例子,你会发现这个例子和这个完全一样。
让我们实践一下
获取 API
让我们看一下 fetch API。fetch(URL) 会返回一个新的 Promise,所以我们可以等待它,但现在我们正在处理网络函数,我们不知道它们是否会被解析,或者只是被拒绝了。所以我们需要处理错误。
fetchUserNames = async (endpoint) => {
try {
const response = await fetch(endpoint);
let data = await response.json();
return data.map(user => user.username);
} catch (error) {
throw new Error(error);
}
}
start = async () => {
userNames = await fetchUserNames('https://jsonplaceholder.typicode.com/users');
console.log(userNames);
fetchUserNames('https://wrong.url').catch(error => console.log(error));
}
start();
您可以在 Async / Await 函数中使用 Try / Catch 以获得更好的错误处理。附带说明一下:nodejs 将退出未捕获错误的进程!您可以将这里的返回值视为承诺中的 resolve 和 throw 拒绝。然后我们使用 fetch API 来获取数据。如您所见,fetch() 调用返回一个承诺。因为我们知道我们得到的是一个 JSON,所以我们在响应上调用 .json(),然后它本身再次为我们返回一个承诺,这就是为什么我们也需要这里 await 的原因。然后我们只提取用户名并返回新创建的数组。我们的启动函数需要是异步的,因为 await 只能在异步函数中调用。我故意在这里混合使用 await 和 promise,向您展示您可以同时使用它们!
koajs 新的 expressjs
app.get("/", async (request, response, next) => {
try {
const finalResult = await database.getStuff();
response.json(finalResult);
} catch (error) {
next(error);
}
});
如果你曾经使用过 expressjs,你就会明白这里发生了什么。koajs 与 expressjs 出自同一批开发人员之手,但从头开始构建,充分利用了 ES6+ 的特性。此外,它还会在必要的情况下使用 Promise。在本例中,我们在“/”路由上处理 HTTP GET 请求。正如你所见,此路由可以异步执行。然后,我们可以在箭头函数中执行任何操作。在这个例子中,你可以想象我们正在调用数据库获取数据,然后将其返回给客户端。
每 X 秒运行一次函数
const waitFor = (ms) => new Promise(r => setTimeout(r, ms));
const start = async () => {
try {
console.log("start");
c = 1;
while(c < 10){
console.log('waiting!');
await waitFor(2000)
console.log('start some function');
await runDBBackup();
if(c === 3){
throw new Error('An error happend');
}
c++
}
console.log('end');
} catch (error) {
console.log(`error: #{error}`);
}
}
start();
好的,以上就是我们学到的知识。首先,我们需要将 setTimeout 包装在一个 Promise 中,它将在 X 秒后解析。就是这样!它不做任何其他事情。它只是暂停执行。然后我们创建启动函数。在本例中,我故意让它在运行 3 次后失败。这就是为什么我们有 c 变量。然后我们将进入 while 循环并等待 2 秒。然后我们将运行备份函数,当它第四次运行时将发生错误。如果将 c < 10 替换为 true,只要没有异常,它就会一直运行。这是一个简单的备份过程实现,它将在 X 时间后运行。
感谢阅读!
打招呼! Instagram | Twitter | LinkedIn | Medium
鏂囩珷鏉ユ簮锛�https://dev.to/lampewebdev/i-promise-this-is-a-practical-guide-to-async--await-39ek