微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Promise发展的心路历程

Promise 发展的心路历程

浅谈啥是异步

在这篇博文中,将会用最简单的例子,简单快速说明什么是异步,回调函数最大的问题是什么——不再是江湖传言的回调地狱。同时,这里认大家都知道 promise 基本使用方式,故一些关于如何使用的 demo 不再给出,本文主要是想聊一聊 promise 是如何发展得到的,看一看这段心路历程。

一个场景-什么是异步

我们可以把程序的运行分为现在正在运行的和将来运行的。想象一个场景,当我们运行到 A 行代码的时候,这一行发出一个请求希望得到一个数据,拿到数据后,接下来下一行(B 行),需要对这个拿到的数据进行处理在返回。就像下面这样的代码一样。

let data=ajax("http://someUrl.com") //A
return data.slice(1,3) //B
********
ajax("http://someUrl.com,function resolveData(data){
    return data.slice(1,3)
})

在这里,如果 A 行并不是一个请求(例如直接这里定义 data 的数据是多少),换句话说不需要第三方来决定它什么时候完成,那么 A 行的时间一定是可控的(只是取决于我们硬件性能或者我们算法的好坏)。我们需要等待 A 行拿到数据后才能使得 B 行运行(B 行可以看作是将来)。在 A 行发出请求后,B 行等待数据进行处理之前,中间这段时间我们可以称之为时间间隙。所以可以说,异步的核心就是管理现在运行和将来运行之间的关系,亦或者说核心就是这段间隙(实际上我们的代码并不会写成上面 AB 行的形式,而是下方回调的样子)。再一思考,异步的目的不就是节约我们的时间吗,让代码在更短的时间内运行完,减少等待,等待这点时间接着干其他的,而正因为有了这样的一个目的,才有了我们看到的回调函数模式的写法吗。说到这里,我也曾想过一个场景,如果给一个 button 添加一个点击,若没有回调的这种写法,我们就只能无止尽的循环判断点击是否已经发生,这样做是十分耗费性能的,如果这样写代码,那么一个页面接下来除了判断点击事件是否发生,便什么都做不了了

回调函数又有什么坑

上文说到,因为要去管理代码现在运行和代码将来运行之间的时间间隙,所以有了异步,异步的核心就是去管理这段关系。因此,回调函数诞生了,回调函数就是就是最简单的方法去管理这段“等待关系”。
既然回调函数这么好,恰如其分的“管理了这段关系”为什么还会说它有坑呢。
这里给出一个 demo

doneA(function(){
    doneC()
    doneD(function(){
        doneF()
    })
    doneE(function(){
        doneG()
        setTimeout(3000,function done(){
            console.log('this is setTimeout')
        })
    })
    doneM()
}) //函数命名与执行顺序无关

这里回调带来的最为明显的问题就是嵌套,太多嵌套真的让人看的脑瓜子疼,而且更为重要的是这这么多重异步亦或者又夹杂着同步代码我们很容易不知道这些个函数是怎么跳转。but,这两点虽然是坑,但仍然不是最为重要的坑。真正的坑还在后面。
这里大坑就是控制权利转移了。

问题和核心在于主动权在谁手里

还是之前的 demo

let data=ajax("http://someUrl.com") //A
return data.slice(1,3) //B

ajax("http://someUrl.com,function resolveData(data){
    return data.slice(1,3)
})

仔细看看,有没有发现写成回调函数的语句除了写法上和 A/B 行语句有区别外,还有什么大的区别。很明显,假想的语句中 返回的 data ,是我们拿到原始数据后,由我们进一步处理了的数据;而下一行中,我们把数据处理函数作为参数传入了 ajax。虽然我们坚信 ajax 作为第三方模块是安全的,但既然是第三方,我们就仍然有理由怀疑它返回给我们的数据,不是按照我们传入的处理函数那样处理的。所以,这里回调函数最大的问题就是安全的问题,也就是控制权的问题。相比回调地狱,最为头疼的还是安全问题,毕竟安全至上。(其实这里还有个由嵌套引发的问题,就是代码如何处理错误逻辑,如果仍然使用 try…catch 的写法将会显得十分的臃肿并还会有其他问题比如说拿不到错误)

要是既能写成同步的写法(功能上像如 A/B 行代那样顺序执行,并且确保主动权有在我们自己的手中),又能如回调函数一样,完美的去管理时间间隙。那有没有这样好的方案了???Promise ! Promise 就是以同步写法实现异步的优秀解决方

有很多文档和博文都谈到说 promise 解决了回掉地狱(callback hell)的问题,并让异步的写法变成了同步,当我读完神书《你不知道的 JavaScript》关于这部分的文章之后,才知道,这里的核心更在于是“主动权在于谁的手中”。做个不完美的比喻,之前回调的方式,就如一个国家由于缺乏石油开采技术,只能把石油开采命脉交由了第三方,开采的时候还需要去协调第三方的时间,而使用 promise 才真正掌握了主权,我们可以根据自己的需求开采,使用第三方而不再依赖第三方。这也意味着使用 promise 会更加安全,毕竟主动权握在了我们的手中。

那接下来就来深入分析下,为什么说,promise 将主动权交回了自己的手中,而之前的 callback 方式就没有在自己的手中。

Promise 来了

刚才说到要是既能写成同步的写法(功能上像如 A/B 行代那样顺序执行,并且确保主动权有在我们自己的手中),又能如回调函数一样,完美的去管理时间间隙,换句话说,就是我们希望有一种方法可以让第三方通知我们它们的任务什么时候结束,这样之后的事情就是我们自己的权利了,这就意味着当我们注册完成这个事情后,我们又可以接着做其他事情,不用一直 waiting 到第三方完成任务。这就是 promise 方法!!!

首先可以我们从中文上的意思来理解,promise 中文为“保证”,它就像去饭店吃饭时候的排队编号,到了这个号码就该我们去吃饭了。
这里结合着《你不知道的 JS》给出一个非常简单的 demo,用以说明 promise 的内核思想是什么:为了统一处理现在和将来,那就把它们都变成将来 (后话:这也许是 promise 具有传递性的原因吧)

第一段代码:
let x,y=3
console.log(x+y)

**********
第二段代码:
let x=ajax("http://getXfromSomeUrl.com")
let y=3
console.log(x+y)

我们很容易知道这段代码打印出的值是 NaN,因为 x 是 undifined,注意这里的前提是两行代码都是同步。如果,这里的代码混杂着异步代码,如上所示(还是按照我们的同步的思维去写,不写成回调函数的样式),最后一行的 console.log 代码依赖于上面两行代码,那么这里就会出现问题了,因为可能会出现两种情况,如果 x 请求正确,第三行就会正确执行,否则第三行就会失败。继续深入思考,如果第三行失败,就意味这我们后续的代码就不能继续执行了,真的令人脑瓜子疼。面对这种情况,当然可以用 callback 的方式重写

function add(getX,getY){ //getX和getY为获得x,y的方法,这样写的好处就是无论同步异步都封装起来了
    let x,y;
    getX(function(xIsGotValue){
        x=xIsGotValue
        //两个都准备好了吗?
        if(y!==undefined){
            console.log(x+y)
        }
    })
    getY(function(yIsGotValue){
        y=yIsGotValue
        //两个都准备好了吗?
        if(x!==undefined){
            console.log(x+y)
        }
    })
}

很好,上面的代码将异步或者同步都封装起来了,我们不再担心异步的成功失败与否带来的多种情况的值了,也不用担心影响后面代码运行的问题了。继续分析上面这段代码,很明显,上面的代码并不用去担心,x 或者 y 现在是否已经可用,换句话说,都把他们当成将来发生的事情,都用回调的方式去处理。好了到这里,可以去谈 promise 了。promise 就是一种更简明的写法,其核心就是归一化,无论同步异步都当作异步来处理,Promise 是一种封装和组合未来值的易于复用的组合

好了,知道了 promise 的核心是归一化,那 promise 是如何解决上文提到的控制权的问题的了,亦或者说promise 是如何来让当第三方完成任务的时候,告知我们的了呢,如果使用过 promise,一定用过 resolve(决议)/reject,resolve 方法就是通知我们的手段。

总结

OK 到这里,我们就大概了解了从异步-》回调-》promise 的心路发展历程,在整个过程中其实有很多细节都是忽略了的,比如说异步中的错误处理,promise 中一些比较 tricky 的内容,比如具有 then 方法的鸭子类型,还有关于事件微任务宏任务的内容以及 promise 原生实现的方法,这部分我将在后续的文章中继续进行整理。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐