自己动手实现promise

来源:互联网 发布:网络代理打鱼怎么判 编辑:程序博客网 时间:2024/06/04 20:08

本文适合对于promise的实现原理感兴趣的同学,由于使用PHP实现promise,故需要具备一定的PHP基础知识。

一、背景

大家都知道,异步编程在web领域内越来越多地运用,但异步回调代码的写法十分恶心,逐层嵌套,不便于阅读。为了解决这个问题,js实现了promise模式,但大多数开发者只知道promise的表面用法,不知其底层实现逻辑。笔者采用PHP实现了自己的promise,借着此过程,与大家分享promise的实现原理。

二、Promise用法

setTimeout(function() {    console.log("timeout1");    setTimeout(function(){        console.log("timeout2");        setTimeout(function(){            console.log("timeout3");        }, 1000);    }, 1000);}, 1000);

通常处理异步的函数采用回调写法,其形式为如上面代码,1000ms超时后终端输出timeout1,随后进入第二层超时函数,再过1000ms之后终端输出timeout2,随后进入第三层超时函数,1000ms之后终端输出timeout3。很明显,函数嵌套会越来越深,对应的功能promise代码实现如下:

var promise = new Promise(function(resolve, reject){    setTimeout(function(){        console.log("timeout1");        resolve();    }, 1000);});promise.then(function(value){    setTimeout(function(){        console.log("timeout2");        resolve();    }, 1000);}).catch(function(error){    console.error(error);}).then(function(value){    setTimeout(function(){        console.log("timeout3");        resolve();    }, 1000);}).catch(function(error){    console.error(error);});

promise对象传入回调函数,回调函数接口包括执行成功时调用的resolve函数和执行失败时调用的reject函数。then方法指定resolve函数的实现,catch指定reject函数的实现。这样就把本该在函数体内嵌套的resolve函数和reject函数放到函数外,最终串在一起,保证函数的嵌套层数不会增加。

三、Promise实现

promise完整实现包括很多功能,本文主要介绍promise、all、race这些常用的函数实现,使用这些基础组件读者可以快速实现更加高级的接口。

注:上面的catch在此处改用otherwise。

1.promise

promise最基本的功能如上所述,难点在于如何将resolve和reject与后面then和catch指定的函数实现绑定起来。我们知道,promise支持同步或异步调用resolve和reject,如何保证then或catch调用的先后顺序极为关键。回顾promise构造形式:

new Promise(function($resolve, $reject){    //$resolve or $reject    ...})

执行promise构造函数传入的参数(参数为一个回调函数),使得resolve或reject被调用。

  • 同步调用场景下resolve和reject真正被调用时其还未绑定到then或otherwise中指定的函数实现,因此需要保存传入resolve或者reject中的参数,这样then或otherwise方法执行时将参数传入其指定的resolve或reject函数执行即可。

  • 异步场景下resolve或reject真正被调用时,then或otherwise已经执行完成,故需要保存then或otherwise指定的resolve或reject函数实现,等到resolve或reject调用时,调用其绑定的函数即可。

这样,实现promise的基本代码框架就出来了:

    public function __construct(callable $callback)    {        //给callback传入自定义的resolve和reject函数,旨在获取调用时的参数        $callback([$this, "resolve"], [$this, "reject"]);    }    public function resolve($value = null)    {        //resolve或者reject函数实现为空,表示同步调用resolve或者reject,        //此时保存参数值,参数值表明这是一个已完成的promise        if ($this->callbackList === []) {            $this->result = new FulfilledPromise($value);            return;        }        //异步调用场景下,callbackList保存了后面then或otherwise的一系列        //函数体列表,逐个判断需要调用then还是otherwise,函数调用结果作为下        //一轮调用的参数值。        $expect = PromiseCallback::THEN;        foreach ($this->callbackList as $callback) {            $type = $callback->getType();            $fn = $callback->getFn();            if ($type === $expect) {                if ($value instanceof PromiseResult) {                    $value = $value->getValue();                }                $value = call_user_func($fn, $value);                if ($value instanceof RejectedPromise) {                    $expect = PromiseCallback::OTHERWISE;                } else {                    $expect = PromiseCallback::THEN;                }            }        }        //result保存为FulfilledPromise或者RejectedPromise        if ($value instanceof PromiseResult) {            $this->result = $value;        } else {            $this->result = new FulfilledPromise($value);        }    }    public function reject($value = null)    {    //reject实现与resolve类似,不同之处在于首轮函数调用需要的是    //PromiseCallback::OTHERWISE类型的函数       ...    }        public function then(callable $resolve)    {        try {        //result为FulfilledPromise时,表示同步调用,直接调用resolve处理            if ($this->result instanceof FulfilledPromise) {                $value = $resolve($this->result->getValue());                if ($value instanceof PromiseResult) {                    $this->result = $value;                } else {                    $this->result = new FulfilledPromise($value);                }            } else if ($this->result == null) {            //result为null表示异步调用,保存resolve函数至函数列表                $this->callbackList[] = new PromiseCallback(PromiseCallback::THEN, $resolve);            }        } catch (\Exception $e) {            var_dump($e);        }        return $this;    }    public function otherwise(callable $reject)    {        //实现与then类似,除了类型判断刚好相反        ...    }

按照上述代码,不管同步还是异步调用场景,最终都会逐层调用then或otherwise中的resolve或reject直到结束。

2.all

all需要实现的功能为:

  • 如果参数数组中所有的promise都调用resolve,将所有resolve调用的参数包装成对应数组传送给then中的resolve实现处理

  • 一旦有一个promise调用的是reject,将reject调用的参数传送给otherwise中的reject实现处理。

因此,需要统计每个promise执行的结果,但是这里又会涉及到同步和异步场景下每个promise调用的问题

对应的解决方案为:

  • 针对每个promise,分别执行then和otherwise,在resolve函数中判断所有promise的结果是否已经完成,如果已经执行完成,传入结果数组回调all[promises]->then->otherwise中的resolve函数。

  • 如果promise的otherwise函数被调用,则回调reject函数。

为了保证后续的then和otherwise都可以被调用,只需要返回promise对象,复用promise的逻辑即可。

实现代码逻辑为:

public function __construct($promises){    if (is_array($promises))        $this->promises = $promises;    else        $this->promises = [];}public function then($callback){...//返回promise对象,使得后续的then和otherwise可以复用promise的逻辑    return new Promise(function ($resolve, $reject) use($callback) {        for ($i = 0; $i < count($this->promises); $i++) {            $promise = $this->promises[$i];            if ($promise instanceof Promise) {                $promise->then(function ($value) use ($resolve, $i, $callback) {                    $this->resolvedValue[$i] = $value;                    //收集所有的resolvedValue结果,一旦全部收集到,                    //调用callback,此时then方法中的resolve被调用。                    //同时为了保证后续的then和otherwise可以调用,                    //需要调用resolve                    if (count($this->resolvedValue) == count($this->promises)) {                        if ($this->arrived === false) {                            $this->arrived = true;                            ksort($this->resolvedValue);                            $value = call_user_func($callback, $this->resolvedValue);                            $resolve($value);                        }                    }                })->otherwise(function ($value) use ($reject) {                //otherwise逻辑里直接调用reject                    if ($this->arrived === false) {                        $this->arrived = true;                        $reject($value);                    }                });            } else if ($promise instanceof RejectedPromise) {            //一旦promises中存在RejectPromise,直接调用reject                if ($this->arrived === false) {                    $this->arrived = true;                    $reject($promise->getValue());                    return;                }            } else {            //其他类型的入参按照resolve来处理                if ($promise instanceof FulfilledPromise) {                    $promise = $promise->getValue();                }                $this->resolvedValue[$i] = $promise;                if (count($this->resolvedValue) == count($this->promises)) {                    if ($this->arrived === false) {                        $this->arrived = true;                        ksort($this->resolvedValue);                        $value = call_user_func($callback, $this->resolvedValue);                        $resolve($value);                    }                }            }        }    });}

3.race

race需要实现的功能为:

参数中所有的promise同时执行,最先完成的promise执行后面的then和otherwise逻辑。 和all的实现不同的是,race只需要处理最先调用resolve或者reject的promise即可。可以采取的策略为:

委托每个promise执行then和otherwise,在resolve和reject函数中通过唯一标记判断是否有其他promise已经完成,如果没有,则置标记为已完成,使用此结果进行后续逻辑。反之不作任何操作。

实现代码逻辑为:

public function then($callback){...//返回promise对象,使得后续的then和otherwise可以复用promise的逻辑    return new Promise(function ($resolve, $reject) use ($callback) {        foreach ($this->promises as $promise) {            if ($promise instanceof Promise) {            //委托每个promise调用then和otherwise                $promise->then(function ($value) use ($callback, $resolve, $reject) {                //$this->arrived标记结果是否已经产生,用于互斥                    if ($this->arrived === false) {                        $this->arrived = true;                        //回到callback并根据结果类型调用reject或resolve                        //用于后续then和otherwise的处理                        $value = call_user_func($callback, $value);                        if ($value instanceof FulfilledPromise) {                            $resolve($value->getValue());                        } else if ($value instanceof RejectedPromise) {                            $reject($value->getValue());                        } else {                            $resolve($value);                        }                    }                })->otherwise(function ($value) use ($reject) {                //otherwise中直接调用reject,此时then中的callback不会调用                    if ($this->arrived === false) {                        $this->arrived = true;                        $reject($value);                    }                });            } else if ($promise instanceof RejectedPromise) {            //一旦promises中存在RejectPromise,直接调用reject                if ($this->arrived === false) {                    $this->arrived = true;                    $reject($promise->getValue());                }            } else {            //其他类型的入参按照resolve来处理                if ($this->arrived === false) {                    $this->arrived = true;                    if ($promise instanceof FulfilledPromise) {                        $promise = $promise->getValue();                    }                    $value = call_user_func($callback, $promise);                    if ($value instanceof FulfilledPromise) {                        $resolve($value->getValue());                    } else if ($value instanceof RejectedPromise) {                        $reject($value->getValue());                    } else {                        $resolve($value);                    }                }            }        }    });}

四、Promise源码

根据上述原理,初步实现了自己的promise,源代码的github地址为https://github.com/xhjcehust/PHPromise,可以安装README.md中的指引安装环境,执行单元测试,验证效果。关于代码有任何建议欢迎反馈,如果觉得代码不错,fork或star是对作者最大的鼓励。

想要获取最新技术文章?欢迎订阅微信公众号----软件编程之路


原创粉丝点击