整理 - Promise 和 Async/Await 用法

Promise 和 Async/Await用法整理

1. Promise

1.1 简介

Promise,简单来说就是一个容器,里面保存着某个未来才会结束得事件(通常是一个异步操作得结果)

Promise 对象得基本语法:

new Promise( (resolve, reject) => {
    // ...
});

从语法上来说,Promise 是一个对象,从它可以获取异步操作得信息。

基本语法:

let p = new Promise( (resolve, reject) => {
    // ...
    resolve('success');
});

p.then( result => {
    console.log(result); // success
})

Promise对象特点和三个状态:

  • Pending
  • value -> Fulfilled
  • error -> Rejected

例如:

let p = new Promise( (resolve, reject) => {
    // ...
    resolve('success');
    console.log('after resolve');
    reject('error');
});

p.then( result => {
    console.log(result);
});

p.catch( result => {
    console.log(result);
})

结果:
after resolve;
success;

resolve 下面得语句其实是可以执行的,那么为什么reject的状态信息在下面没有接收到呢?这就是因为 Promise 对象的特点:状态的凝固。 new 出一个 Promise 对象时,这个对象的起始状态就是 Pending 状态,在根据 resolve 或 reject 返回 Fulfilled 状态/ Rejected 状态。

1.2 then

Promise.prototype.then
它的作用是为 Promise 实现添加状态改变时的回调函数,参数个数可以有两个。

  • then(func1, func2)
  • then(func1)
  • then(null, func2)

Then 分别接受 resolve 和 reject 的信息,有三种参数形式,第三种比较“怪异”,只用来接受做 reject 处理。

eg:

let p = new Promise( (resolve, reject) => {
    // ...
    let random = Math.random(); // 小于1大于0
    if( random > 0.4 ){
        resolve('random > 0.4')
    } else {
        reject('random <= 0.4')
    }
});

p.then( result => {
    console.log('resolve', result);
}, result => {
    console.log('reject', result);
});

1.3 链式调用

我们来执行一段代码:

let p = new Promise( (resolve, reject) => {
    reject('reject');
});

let resultP = p.then(null, result => {
    console.log(result);
});

console.log(resultP);

结果:

Promise {}
reject

js的执行顺序就是这样, 同步 -> 异步 -> 回调,在同步执行的时候,Promise 对象还处于 pending 的状态,也说明了这个 then 返回的是一个 Promise 对象。

而且必须在 then 里面给一个Promise,才能继续调用,否则 undefined。

eg:

let p = new Promise((resolve, reject) => {
  reject("error");
});
let resultP = p.then(null, (result) => {
  console.log(result);
  return new Promise((resolve, reject) => {
    reject("error");
  });
});

resultP.then(
  (tmp) => {
    console.log(tmp, 1);
  },
  (tmp) => {
    console.log(tmp, 2);
  }
);

结果:

error
error 2

1.4 catch

Promise.prototype.catch
用于指定Promise 状态变为rejected 时的回调函数,可以被认为是then(null, func2)的简写形式。

eg:


let p = new Promise((resolve, reject) => {
  reject("error");
});
p.catch((result) => {
  console.log(result, 0);
});
p.then(null, (tmp) => {
  console.log(tmp, 1);
});
p.catch((result) => {
  console.log(result, 2);
});

0
1
2

1.5 Promise.resolve()

将现有对象转为一个resolve状态的 Promise 对象的快捷方式。
传入一个普通的对象:

let p1 = Promise.resolve({
  name: "lemon",
  age: "xxxx",
});
p1.then((result) => {
  console.log(result);
});

如果是Promise对象呢,直接返回

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  }, 500);
});
let pp = Promise.resolve(p);
pp.then((result) => {
  console.log(result);
});
console.log(pp === p);

true
success

1.6 Promise.reject()


let p = Promise.reject(123);
console.log(p);
p.then((result) => {
  console.log(result);
}).catch((result) => {
  console.log("catch", result);
});


Promise { 123 }
catch 123

1.7 Promise.all()

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。成功和失败的返回的数据类型是不相同的。

eg:

let p1 = Promise.resolve(123);
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("hello");
  }, 500);
});
let p3 = Promise.resolve("success");
Promise.all([p1, p2, p3]).then(
  (result) => {
    console.log(result);
  },
  (error) => {
    console.log(error);
  }
);
console.log("---");

'---'
[123, 'hello','success']

成功之后就是数组类型,当所有状态都是成功状态才返回数组,只要其中有一个的对象是 reject 的,就返回 reject 的状态值。

eg:

let p1 = Promise.resolve(123);
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("hello");
  }, 500);
});
let p3 = Promise.resolve("success");
Promise.all([p1, p2, p3]).then(
  (result) => {
    console.log(result);
  },
  (error) => {
    console.log(error);
  }
);

'hello'

有一个eg:

//用sleep来模仿浏览器的AJAX请求
function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res(new Date());
    }, wait);
  });
}
let p1 = sleep(5000);
let p2 = sleep(10000);
let p3 = sleep(20000);
Promise.all([p1, p2, p3]).then((result) => {
  console.log(result);
  //.....
  //loading
});

58:55
59:01
59:11

可以看出 Promise.all 中的 Promise 几乎是同时开始的

1.8 Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

和 all 同样接受多个对象 , 不同的是,race()接受的对象中,那个对象返回的快就返回那个对象,就如race直译的赛跑这样。如果对象其中有 reject 状态的,必须 catch 捕捉到,如果返回的够快,就返回这个状态。race 最终返回的只有一个值。

eg:


let p1 = sleep(500);
let p0 = sleep(2000);
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("error");
  }, 1000);
});
Promise.race([p1, p0]).then((result) => {
  console.log(result);
});
Promise.race([p2, p0])
  .then((result) => {
    console.log(result);
  })
  .catch((result) => {
    console.log(result);
  });
Promise.race([p1, p2])
  .then((result) => {
    console.log(result);
  })
  .catch((result) => {
    console.log(result);
  });

500
500
error

1.9 异常处理

错误处理的声音实在安静,安静得听不见 from Nolan Lawson

当 promise 被明确拒绝时,会发生拒绝;但是如果是在构造函数回调中引发得错误,则会隐式拒绝。

为什么说安静,一个例子,Promise 内部得错误外界用try-catch捕捉不到。可用用then或catch捕获到。

eg:

try {
  let p = new Promise((resolve, reject) => {
    throw new Error(" I'm error");
  });
  p.then(
    (res) => {
      console.log("resolve", res);
    },
    (error) => {
      console.log("reject", error);
      p.catch((reject) => {
        console.log("Promise.catch");
      });
    }
  );
} catch (error) {
  console.log("catch", error);
}

reject Error: I'm error
Promise.catch

所以:
建议,在 promise的链的尾部必须要有个 catch 接着

2. Async-Await

2.1简介

Async-Await:
异步编程的最高境界,就是根本不用关心它异步。async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。

async-await 与 promise 的关系:
不存在谁替代谁,因为 async-await 是寄生于 Promise。Generator 的语法糖。

async 和 await 在干什么,async 用于声明一个 function 是异步的,而 await 可以认为是 async wait 的简写, 等待一个异步方法执行完成。

2.2 基本语法

async function demo(params) {

}

demo();

async 函数返回的是一个 Promise 对象。
必须了解的 asyncFunction

console.log(async function(){}.constructor);

在Chrome里申明这样一个函数,可以在控制台看到返回的其实就是一个Promise对象。
扩展需要了解的就是Chrome现在也支持asyncFunction,可以在Chrome控制台测试:
console.log(async function(){}.constructor);
ƒ AsyncFunction() { [native code] }

2.3 规则

  1. async 表示这是一个 async 函数, await 只能用在这个函数里面。
  2. await 表示在这里等待 promise 返回结果后,再继续执行。
  3. await 后面跟着的应该是一个 promise 对象(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了...)
await放在普通函数里是会报错的。

await可以理解为 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用。

await等待的虽然是 promise 对象,但不必写.then(...),直接可以得到返回值。

eg:

async function demo() {
  let result = await Promise.resolve(123);
  console.log(result);
}
demo();

2.4 应用

Promise 虽然一方面解决了 callback 的回调地狱,但是相对的把回调“纵向发展”了,形成了一个回调链。

eg:


function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res(wait);
    }, wait);
  });
}
sleep(100)
  .then((result) => {
    return sleep(result + 100);
  })
  .then((result02) => {
    return sleep(result02 + 100);
  })
  .then((result03) => {
    console.log(result03);
  });

300

后面的结果都是依赖前面的结果。
改成 async/await 写法就是:


function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res(wait);
    }, wait);
  });
}
async function demo() {
  let result01 = await sleep(100);
  // 上一个await执行之后才会执行下一句
  let result02 = await sleep(result01 + 100);
  let result03 = await sleep(result02 + 100);
  return result03;
}
demo().then((result) => {
  console.log(result);
});

因为 async 返回的也是 promise 对象,所以用 then 接受就行了。

300

需要注意的就是 await 是强制把异步变成了同步,这一句代码执行完,才会执行下一句。

2.5 错误处理

既然 .then(...) 不用写了,那么 .catch(...) 也不用写,可以直接用标准的 try catch 语法捕捉错误。


async function demo() {
  let result01 = await sleep(100);
  // 上一个await执行之后才会执行下一句
  let result02 = await sleep(result01 + 100);
  let result03 = await sleep(result02 + 100);
  try {
    let result04 = await new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(100);
      }, 100);
    });
    return result04;
  } catch (e) {
    console.log("error", e);
  }
}
let p = demo().then(
  (data) => {
    console.log(data, "1");
  },
  (e) => {
    console.log(e, "2");
  }
);

结果:

error 100
undefined "1"

这是基本的错误处理,但是当内部出现一些错误时,包括有一个Promise reject 了,就会抛出错误且下面的代码不会执行。


function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      rej(wait);
    }, wait);
  });
}
async function demo() {
  let a = await sleep(100);
  let b = await sleep(200);
  console.log(2);
}
demo();

Uncaught (in promise) 100

处理异常时,如果 async 中已经处理过了则 .catch()将捕捉不到。如果 async 没有处理,则 .catch()可以捕捉到。

eg. async 中处理:


function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      rej(wait);
    }, wait);
  });
}
async function demo() {
  try {
    let result04 = await sleep(100);
  } catch (e) {
    console.log("error", e);
  }
}
let p = demo().catch((e) => {
  console.log("promise catch", e);
});

'error' 100

eg. async 外部处理:


function sleep(wait) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      rej(wait);
    }, wait);
  });
}
async function demo() {
  let result04 = await sleep(100);
}
let p = demo().catch((e) => {
  console.log("promise catch", e);
});

'promise catch' 100

2.6 注意你的并行执行和循环

如果这三个是你想异步发出的AJAX请求,在这段代码里其实是同步的,第一个发出去被处理后( resolve/reject )才会处理第二个,所以 async / await 需要谨慎使用。

eg.1


function sleep(wait) {
  return new Promise((resolve, rej) => {
    console.log(`wait:  ${wait}`);
    setTimeout(() => {
      resolve(wait);
      console.log(`resolve: ${wait}`);
    }, wait);
  });
}
async function demo() {
  let result01 = await sleep(10000);
  let result02 = await sleep(20000);
  let result03 = await sleep(30000);
}
let p = demo().catch((e) => {
  console.log("promise catch", e);
});

wait: 10000
一段时间后
resolve: 10000
wait: 20000
一段时间后
resolve: 20000
wait: 30000
一段事件后
resolve: 30000

eg. 2

function sleep(wait) {
  return new Promise((resolve, rej) => {
    console.log(`wait:  ${wait}`);
    setTimeout(() => {
      resolve(wait);
      console.log(`resolve: ${wait}`);
    }, wait);
  });
}
function sleep2(wait) {
  return new Promise((resolve, rej) => {
    console.log(`wait:  ${wait}`);
    setTimeout(() => {
      rej(wait);
      console.log(`reject: ${wait}`);
    }, wait);
  });
}
async function demo() {
  let result01 = await sleep(100);
  try {
    let result02 = await sleep2(200);
  } catch (error) {
    let result03 = await sleep(300);
  }
}
let p = demo().catch((e) => {
  console.log("promise catch", e);
});

'wait:' 100
'resolve': 100
'wait': 200
'reject': 200
'wait': 300
'resolve': 300

由于错误在内部处理没有作为 Promise 抛出所以 demo().catch() 捕捉不到。

async 中异步任务

在 async 中使用 Promise.all([ ]) 生成一个新的 Promise,来达到异步任务效果。


function sleep(wait) {
  return new Promise((resolve, rej) => {
    console.log(`wait:  ${wait}`);
    setTimeout(() => {
      resolve(wait);
      console.log(`resolve: ${wait}`);
    }, wait);
  });
}
async function demo() {
  let res = Promise.all([(sleep(1000), sleep(1000), sleep(1000))]);
}
let p = demo().catch((e) => {
  console.log("promise catch", e);
});

'wait:' 10000
'wait:' 10000
'wait:' 10000
一段时间后
resolve: 10000 * 3 (几乎同时)

现有一些 forEach 或者 map 的循环里,比如在 forEach 里使用 await,这时候的上下文就变成了 array,而不是 async function,就会报错。这时候你就要想到是什么错误。

参考:https://www.jianshu.com/p/fe0159f8beb4

posted @ 2020-10-15 11:02  lemon-Xu  阅读(643)  评论(0编辑  收藏  举报