如何实现异步

异步:

同步是像排队一样,只有前一个任务执行完,才能执行下一个。而异步就是打乱这种顺序,当前一个任务宕机了,被挂起了,可以跳过它,接着往下执行。或者干脆将可能会失败或卡住的这种任务放到最后执行。这就叫做异步

异步任务

异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步任务还可分为宏任务和微任务

宏任务和微任务是异步里 api 的分类;

1.微任务是**es 提供的 api,**常用的有: promise 实例.then().catch() & 异步函数中 await 行后面的代码 ;

2**.宏任务浏览器提供的 api**,常用的有 :定时器 & ajax & dom 事件;

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function fn1() {
//!!!await 行及前面的代码都是同步
console.log("async1 start");
await fn2();
//!!!await行后的代码:异步,微任务
console.log("async1 end");
}
async function fn2() {
console.log("async2 ");
}
console.log("第1轮宏任务 start,执行all同步代码");
fn1();
setTimeout(() => console.log("第2轮宏任务"), 1000);
new Promise((res, rej) => {
console.log("promise start");
res("promise then");
}).then((e) => console.log(e));
console.log("第1轮宏任务 end,执行第1轮微任务");

第 1 轮宏任务 start,执行 all 同步代码
async1 start
async2
promise start
第 1 轮宏任务 end,执行第 1 轮微任务
async1 end
promise then
第 2 轮宏任务
在编程中,如请求资源失败,若没有使用异步编程,页面就会卡死,这非常不友好
那么,我们如何异步呢?

1.回调函数+定时器异步(宏任务)

这是异步编程最基本的方法。

假定有两个函数 f1 和 f2,后者等待前者的执行结果

如果 f1 是一个很耗时的任务,可以考虑改写 f1,把 f2 写成 f1 的回调函数。

然后,使用 setTimeout 方法,将可能造成阻塞的部分作为异步宏任务执行(setTimeout 是浏览器 api,是宏任务)

1
2
3
4
5
6
function f1(callback) {
setTimeout(function () {
// f1的逻辑代码
callback();
}, 1000);
}

执行代码就变成下面这样:

1
f1(f2);

但是,逻辑比较复杂的时候回调会层层嵌套形成回调地狱,比利于理解和维护
我们就需要更清晰的方法进行异步编程–

2.Promise

它是为了解决异步处理回调地狱而产生的,在语法上它是一个构造函数,接受一个回调函数作为参数,允许我们在其中处理业务逻辑,这个回调函数接收两个参数分别是 resolve 和 reject,二者都是由 JavaScript 引擎提供,不用自己传入。

通过 new Promise()生成的 Promise 实例对象,有三种状态

Promise 对象的三种状态

  • pending 初始状态
  • fulfilled 成功状态
  • rejected 失败状态

resolve 和 reject

  • resolve 方法的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),我们必须在异步操作成功时调用,然后将异步操作的结果,作为参数传递出去
  • reject:让 Promise 对象的状态从 pending==>rejected ,也需要以参数形式将异步处理的结果传出
  • 所谓传递到外面,指的是就是传递到回调函数的外部的–刚 new 出来的 Promise 实例对象的.then 方法);

.then 方法

  • 它默认传入两个回调函数作为参数(也可以只写一个成功的),第一个用于接收resolve()传入的数据,第二个用于接收reject()传入的参数(一般为 Error 信息)
1
2
3
4
pro.then(
function (value) {},
function (reason) {}
);

3.Generator 生成器

生成器本质上是一种特殊的迭代器,使用生成器异步编程往往需要配合 Promise 使用。

举个雷子:依次读取两个文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fs = require("fs");
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function (error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile("/etc/fstab");
var f2 = yield readFile("/etc/shells");
console.log(f1.toString());
console.log(f2.toString());
};

4.async/await 基本用法

async 函数就是 Generator 函数的语法糖

注意

async 异步函数本身是同步的,await 所在语句后面的代码才是**异步**执行,(微任务)

上面生成器的代码写成 async 函数,就是下面这样。

1
2
3
4
5
6
var asyncReadFile = async function () {
var f1 = await readFile("/etc/fstab");
var f2 = await readFile("/etc/shells");
console.log(f1.toString());
console.log(f2.toString());
};

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
另一个注意点:

await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}

参考:

谈谈我对 Promise 的理解(一)– Macchiato_go | 简书

js 宏任务和微任务–告诉太阳 | CSDN

async 函数的含义和用法–阮一峰

JavaScript 异步编程的四种方法–阮一峰

ES6-Promise:解决回调地狱–Crushdada’s Notes

ES7-新特性:async&await–Crushdada’s Notes

JavaScript-手撕bind方法? 如何判断一个数据是NaN?

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×