Posts 同步/异步
Post
Cancel

同步/异步

1. 同步和异步

js是单线程的,同时只能有一个任务在主线程允许,而像api请求这种,需要等到请求完成拿到数据后才能继续向下执行的任务成为异步任务,在请求未完成前,将任务放到任务队列中,等到请求完成,再通过回调等方式通知主线程。

2. promise

  • pending
  • fulfilled
  • rejected
2-1. promise 对象是一个构造函数
1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
});
2-2. Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
1
2
3
4
5
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
2-3. Promise中resolve(promise实例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail
`上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。
由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。
所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。`
2-4. 调用resolve或reject并不会终结 Promise 的参数函数的执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})
2-5. Promise.all()
  • Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
`特例:如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法`
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e); `相当于resolved`

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
p1会resolved,p2首先会rejected,但是p2有自己的catch方法,
该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。
该实例执行完catch方法后,也会变成resolved,
导致Promise.all()方法参数里面的两个实例都会resolved,
因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
2-6. Promise.race([p1, p2, p3])
  • 只要p1、p2、p3之中有一个实例率先改变状态,Promise.race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给Promise.race的回调函数
2-7. Promise.allSettled([p1, p2, p3])
  • p1、p2、p3 都结束, 包含了”fulfilled“和”rejected“两种情况
1
2
3
4
5
6
7
8
9
10
11
12
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
2-5. Promise.any()
  • 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

3. async/await

  • async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
  • async函数内部return语句返回的值,会成为then方法回调函数的参数。
  • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态
1
2
3
4
5
6
7
8
9
10
11
12
async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了
  • async必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变
await
  • await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
  • 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。 ```markdown async function f() { await Promise.reject(‘出错了’); await Promise.resolve(‘hello world’); // 不会执行 }

f() .then(v => console.log(v)) .catch(e => console.log(e))

1
2
3
4
5
6
7
8
9
10
11
12
13
- 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
```markdown
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
  • 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。 ```markdown async function f() { await Promise.reject(‘出错了’) .catch(e => console.log(e)); return await Promise.resolve(‘hello world’); }

f() .then(v => console.log(v)) // 出错了 // hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
```markdown
let foo = await getFoo();
let bar = await getBar();
`这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。`
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
按顺序完成异步操作
1
2
3
4
5
6
7
8
9
10
11
12
async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
This post is licensed under CC BY 4.0 by the author.