promise与异步的总结-await-async


JS异步的发展历程是callback->promise/generator->async/await

1. Promise

1.1 带着问题看 Promise

  1. 了解 Promise 吗?
  2. Promise 解决的痛点是什么?
  3. Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
  4. Promise 如何使用?
  5. Promise 常用的方法有哪些?它们的作用是什么?
  6. Promise 在事件循环中的执行过程是怎样的?
  7. Promise 的业界实现都有哪些?
  8. 能不能手写一个 Promise 的 polyfill。

1.2 Promise 出现的原因?

在 Promise 出现以前,我们处理一个异步网络请求,大概是这样:

// 请求 代表 一个异步网络调用。
// 请求结果 代表网络请求的响应。
请求1(function(请求结果1){
    处理请求结果1
})

看起来还不错。
但是,需求变化了,我们需要根据第一个网络请求的结果,再去执行第二个网络请求,代码大概如下:

请求1(function(请求结果1){
    请求2(function(请求结果2){
        处理请求结果2
    })
})

这样看起来就要复杂得多,于是当这种情况增加时,我们就会陷入回调地域回调地域会让我们的代码非常难维护。

于是 Promise 规范诞生了,并且在业界有了很多实现来解决回调地狱的痛点。比如业界著名的 Q 和 bluebird,bluebird 甚至号称运行最快的类库。

看官们看到这里,对于上面的问题 2 和问题 7 ,心中是否有了答案呢。^_^

1.3 什么是 Promise

Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。

(1) 代码书写比较

还是使用上面的网络请求例子,我们看下 Promise 的常规写法:

new Promise(请求1)
    .then(请求2(请求结果1))
    .then(请求3(请求结果2))
    .then(请求4(请求结果3))
    .then(请求5(请求结果4))
    .catch(处理异常(异常信息))

比较一下这种写法和上面的回调式的写法。我们不难发现,Promise 的写法更为直观,并且能够在外层捕获异步函数的异常信息。

(2) API

Promise 的常用 API 如下:

  • Promise.resolve(value)

    类方法,不需要return,该方法返回一个以 value 值解析后的 Promise 对象
    (1) 如果这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)。
    (2) 传入Promise返回Promise本身。
    (3) 其他情况以该值为成功状态返回一个 Promise 对象。

上面是 resolve 方法的解释,传入不同类型的 value 值,返回结果也有区别。这个 API 比较重要,建议大家通过练习一些小例子,并且配合上面的解释来熟悉它。如下几个小例子:

传入promise返回promise本身。

function fn(resolve){
    setTimeout(function(){
        resolve(123);
    },3000);
}
let p0 = new Promise(fn);
let p1 = Promise.resolve(p0);
// 返回为true,返回的 Promise 即是 入参的 Promise 对象。
console.log(p0 === p1);

传入 thenable 对象,返回 Promise 对象跟随 thenable 对象的最终状态。

ES6 Promises 里提到了 Thenable 这个概念,简单来说它就是一个非常类似 Promise 的东西。最简单的例子就是 jQuery.ajax,它的返回值就是 thenable 对象。但是要谨记,并不是只要实现了 then 方法就一定能作为 Promise 对象来使用。

//如果传入的 value 本身就是 thenable 对象,返回的 promise 对象会跟随 thenable 对象的状态。
let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象
promise.then(function(value){
   console.log(value);
});

其它情况,返回一个状态已变成 resolved 的 Promise 对象。

let p1 = Promise.resolve(123); 
//打印p1 可以看到p1是一个状态置为resolved的Promise对象
console.log(p1)
//123
console.log(p1.then(res => console.log(res)));
  • Promise.reject

    类方法,且与 resolve 唯一的不同是,返回的 promise 对象的状态为 rejected。

  • Promise.prototype.then

    实例方法,可能需要return,可以设置两个参数,为 Promise 两种状态注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。

关于.then 的各种输出情况,点这

//可设置两个参数 p.then(onFulfilled[, onRejected]);
Promise.resolve("成功").then(res => {console.log(res)},error =>{console.log(error)})
//成功
  • Promise.prototype.catch

    该catch()方法处理Promise被拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)相同。这意味着 onRejected即使您想回退到一个 undefined结果值,您也必须提供一个函数——例如obj.catch(() => {})。

注意因为抛出异常会返回rejected状态,所以catch也能捕获异常。可以搭配then完成链式操作

//(1)基本使用
Promise.reject("失败").catch(error =>{console.log(error)})//失败
//等同于
Promise.reject("失败").then(undefined,error =>{console.log(error)})


//(2)搭配then使用
var p1 = new Promise(function(resolve, reject) {
  resolve('成功');
});

p1.then(function(value) {
  console.log(value); //成功
  throw '异常信息';
}).then(function(){
  console.log('跳过'); // 没有resolved状态,跳过
}).catch(function(e) {
  console.log(e); // 异常信息
});
  • Promise.prototype.finally

    finally()返回一个方法Promise。在结束时,无论结果是否被履行或被拒绝,都会执行指定的功能完成。这为在Promise成功后都需要执行的代码提供了一种方式。这同样的语句需要在then()和catch()中各写一次的情况。

  • Promise.race

    类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。

  • Promise.all

    类方法,多个 Promise 任务同时执行。
    如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

以上便是常用的API了

(3) 链式操作

出门上班,想自己做饭但时间不够,那么把事情交给保姆:

  • 你先去超市买菜。
  • 用超市买回来的菜做饭。
  • 将做好的饭菜送到我单位。
  • 送到单位后打电话告诉我。

上面三步都是需要消耗时间的,我们可以理解为三个异步任务。利用 Promise 的写法来书写这个操作:

function 买菜(resolve,reject) {
    setTimeout(function(){
        resolve(['西红柿''鸡蛋''油菜']);
    },3000)
}
function 做饭(resolve, reject){
    setTimeout(function(){
        //对做好的饭进行下一步处理。
        resolve ({
            主食: '米饭',
            : ['西红柿炒鸡蛋''清炒油菜']
        })
    },3000) 
}
function 送饭(resolve,reject){
    //对送饭的结果进行下一步处理
    resolve('送达');
}
function 电话通知我(){
    //电话通知我后的下一步处理
    给保姆加100块钱奖金;
}

那么在执行时,有以下链式操作

// 告诉保姆帮我做几件连贯的事情,先去超市买菜
new Promise(买菜)
//用买好的菜做饭
.then((买好的菜)=>{
    return new Promise(做饭);
})
//把做好的饭送到老婆公司
.then((做好的饭)=>{
    return new Promise(送饭);
})
//送完饭后打电话通知我
.then((送饭结果)=>{
    电话通知我();
})

至此,我通知了保姆要做这些事情,然后我就可以放心去上班了。

上面举的例子,除了电话通知我是一个同步任务,其余的都是异步任务,异步任务 return 的是 promise对象。

(4) Promise的三个状态

  • pending,异步任务正在进行。
  • resolved (也可以叫fulfilled),异步任务执行成功。
  • rejected,异步任务执行失败。

(5)使用总结

首先初始化一个 Promise 对象,可以通过两种方式创建,
这两种方式都会返回一个 Promise 对象。

new Promise(fn)
Promise.resolve(fn)

然后调用上一步返回的 promise 对象的 then 方法,注册回调函数。

then 中的回调函数可以有一个参数,也可以不带参数。如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数。比如

new Promise(fn)
.then((res)=>{
    //...
})

最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常。

new Promise(fn)
.then((res)=>{
//...
})
.catch((err)=>{
//...
})

通常按照这三个步骤,你就能够应对绝大部分的异步处理场景。用熟之后,再去研究 Promise 各个函数更深层次的原理以及使用方式即可。

(6) 事件循环

then、 catch 、finally属于微任务

Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环。

(7) 实例

//实测jquery1.10中有then方法,百度搜索用的是这个版本,可在那控制台使用 
function ajaxTest(resolve){
$.ajax({type:"post",url:'https://server.guet.link/app/user/login'}).then(res =>{
    resolve(res)
})
}

new Promise(ajaxTest)
.then((res)=>{
    // 注意
    // axios不需要new Promise 返回 
    // jq ajax 有 thenable 也可以不用 这里是为了方便理解

    // console.log("第1个then",res)
return new Promise(ajaxTest)
})
.then((res)=>{
    // console.log("第2个then",res)
    return new Promise(ajaxTest)
})
.catch((err)=>{
    console.log(err)
})
.finally(()=>{ 
    //isLoading = false 
});

2. generatir以及async/await语法糖

ES6 出现了 generator 以及 async/await 语法糖,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致。

async/await是generator迭代函数的语法糖,关于generator请看另一篇

上面的列子可以写成这样:

function ajaxTest(resolve){
$.ajax({type:"post",url:'https://server.guet.link/app/user/login'}).then(res =>{
    console.log("...网络访问完毕",res)
    resolve(res)
})
}

(async ()=>{
    let loginRes = await new Promise(ajaxTest);
    let reloginRes = await new Promise(ajaxTest);
    console.log("两次结果一览",loginRes,reloginRes);
})()

async/await设计初衷并不是为了取代Promise,而是为了让使用Promise更加方便。-译文

async/await设计初衷并不是为了取代Promise,而是为了让使用Promise更加方便。-原文


文章作者: iamfugui
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 iamfugui !
评论
  目录