實際情況來看,還是yield很爽


0 引言

最近公司有一個 php 的項目,要 port 到 node.js 來。我之前沒有接觸過這個項目,整個項目使用的是 yaf 框架。整個項目流程是調用服務端的業務數據,然后拼裝數據,返回給前端;前端沒有做到前后端分離,還有很多的頁面在服務器端進行渲染 。

1 難度

1.1 php

php 之前沒有寫過幾行代碼,但是還能看懂。yaf 也沒有接觸過,只到聽說的程度。然后還要把一個商品的詳情頁面從 php 導成 js 的,我覺得難度很大,因為接口竟然沒有文檔,只能看着 php 代碼來了解。看了兩天,有點門路了,開始動手。

1.2 js

nodejs使用的是 express 框架,框架不是我選的,公司有架構師選擇,只能聽命。最主要的是 express 確實不難。

1.3 再次申明

接手這一個項目我對這個項目的業務內容知識為零,同志們想想怎么把php導到js.

2 核心:php是同步的,js是異步

這個工作中,最有意思的是,php 是同步的,js 是異步的,把一大把同步的 php 代碼導成異步的 js,這不就是證明 php 是不是最好的語言的戰場嗎?

各位,你們覺得這個事該怎么做快?

2.1 思路

當時,我就想到找個人好好給我講講這個項目的結構阿,字段啊,什么的?發現這樣不行,為什么呢?

  1. 沒有文檔,包括接口
  2. 別人都很忙,沒有時間給你講
  3. 給自己的時間也不多(要在一個星期內搞定)
  4. 給你講了一大堆業務,你也記不住

於是,我就直接上代碼了,決定先寫代碼,業務知識什么以后再補。於是這場戰役就是不關業務,較純的關於代碼的比較了。

2.2 如何做-代碼

導代碼,其實難度不大,就語言而言,php 和 js 相差不大,php 中也就array難以理解,因為能代表兩種數據結構,返回值的時候就要好好看看,到底是返回 [] 還是 {},就這個犯了不少錯,其它的就一般般了。

重點是 php 中對 業務API的訪問是同步的,那叫一個簡單,調用一個函數就可以了。而在 js 中就有一個大問題,js是異步的,你必須將使用結果寫在回調中。一個回調還是沒有問題,問題是一個函數中有多異步調用,這我還能忍。最后面一個問題直接擊垮了我,因為異步,打斷了寫代碼結構層次,而我又不懂業務,接不上了業務邏輯,這就要我了解業務,可惜基於上面的原因不行啊。

有人會說 promise 能解決你的問題,promise 順序,但是怎么訪問變量啊,幾十個變量的修改,你是不是都要放在then中,還有是放在全局變量中,關鍵是then也破壞了整體代碼結構,一眼看過去全是then,最后我也不得不放棄這個方案。

一直說async/await解決了回調的問題,自己也試過,挺好的。可是項目不讓用啊。

直到我發現了co,隨后又發現 bludbird 的 Promise.coroutine,才解決了我的一些問題,因為我就需要直譯啊,就是照着php翻譯到js過去。對比一下代碼:

2.3 php業務代碼

public static function getProductInfo($productId, $goodsId, $uid, $vipLevel = 0, $productSkn = null) 
    {
        $goodsInfo = array();
        $statGoodsInfo = array();
        $banner = array();
        $baseInfo = ItemData::baseInfo($productId, $uid, $productSkn);//異步
        if(empty($baseInfo['productName']) && empty($baseInfo['erpProductId']) && empty($baseInfo['productPriceBo'])) {
            return array();
        }
        $baseInfo['uid'] = $uid;
        $productId = $baseInfo['id'];
        $goodsInfo['name'] = $baseInfo['productName'];
        $goodsInfo['skn'] = $baseInfo['erpProductId'];
        $goodsInfo['productId'] = $productId;
        $goodsInfo['maxSortId'] = $baseInfo['maxSortId'];
        $goodsInfo['smallSortId'] = $baseInfo['smallSortId'];
        $goodsInfo['promotionId'] = $baseInfo['isPromotion'];
        $goodsInfo['goCartUrl'] = Helpers::url('/shopping/cart');
        $brandId = 0;
        //設置並發請求數據
        self::setMultiResourceByProductBaseInfo($baseInfo);//異步
        if(isset($baseInfo['brand']['id'])) {
            $brandId = $baseInfo['brand']['id'];
        }
        //收藏喜歡
        $favoriteData = self::getProductFavoriteData($uid, $productId, $brandId);//異步

        // 商品標簽
        $goodsInfo['tags'] = self::getTagsDataByProductInfo($baseInfo);//異步
        
        // 商品促銷短語
        if (!empty($baseInfo['salesPhrase'])) {
            $goodsInfo['saleTip'] = $baseInfo['salesPhrase'];
        }
        
        // 商品價格
        if (isset($baseInfo['productPriceBo'])) {
            $goodsInfo['marketPrice'] = $baseInfo['productPriceBo']['formatMarketPrice'];
            $goodsInfo['hasOtherPrice'] = true;//非市場價格
            $goodsInfo['salePrice'] = $baseInfo['productPriceBo']['formatSalesPrice'];
            if($goodsInfo['marketPrice'] == $goodsInfo['salePrice']) {//價格相同,只顯示市場價格
                unset($goodsInfo['salePrice']);
                $goodsInfo['hasOtherPrice'] = false;
            }
        }
        
        //VIP數據
        $goodsInfo['vipPrice'] = self::getVipDataByProductBaseInfo($baseInfo, $vipLevel, $uid);//異步
        //促銷活動banner
        $goodsInfo['activity'] = self::getActivityDataByProductBaseInfo($baseInfo);//異步
        
        if (isset($baseInfo['productPriceBo']['yohoCoinNum']) && $baseInfo['productPriceBo']['yohoCoinNum'] !== 0) {
            array_push($goodsInfo['activity'],
                array('type' => '返YOHO幣', 'des' => '每件返 ' . $baseInfo['productPriceBo']['yohoCoinNum'] . '個 YOHO幣')
            );
        }

        // 上市期
        if (isset($baseInfo['expectArrivalTime']) && !empty($baseInfo['expectArrivalTime'])) {
            $goodsInfo['arrivalDate'] = $baseInfo['expectArrivalTime'] . '月';
            $goodsInfo['presalePrice'] = $baseInfo['productPriceBo']['formatSalesPrice'];
            unset($goodsInfo['salePrice']);
            $goodsInfo['hasOtherPrice'] = false;
        }
        
        //商品咨詢和評論數據
        $consultComment = self::getConsultCommentDataByProductInfo($baseInfo);//異步
        // 品牌信息
        if (!empty($baseInfo['brand'])) {
            $goodsInfo['brandImg'] =  Helpers::getImageUrl($baseInfo['brand']['brandIco'], 47, 47);
            $goodsInfo['brandName'] = $baseInfo['brand']['brandName'];
            $goodsInfo['brandUrl'] = Helpers::url('', array(), $baseInfo['brand']['brandDomain']);
            $banner = self::getBrandDataByProductBaseInfo($baseInfo);//異步
            if(isset($banner['isCollect']) && isset($favoriteData['brand'])) {
                $banner['isCollect'] = $favoriteData['brand'];
            }
        }
        
        //sku商品信息
        $skuData = self::getSkuDataByProductBaseInfo($baseInfo);//異步
        $goodsInfo['img'] = $skuData['defaultImage'];
        $goodsInfo['colors'] = $skuData['skuGoods'];
        $totalStorageNum = $skuData['totalStorageNum'];
        
        // 是否收藏
        $goodsInfo['isCollect'] = $favoriteData['product'];
        // 限購商品
        if ($baseInfo['isLimitBuy'] === 'Y') {
            // 是否開售
            $isBeginSale = (isset($baseInfo['saleStatus']) && $baseInfo['saleStatus'] == 1);
            // 限購商品有關的展示狀態
            $showStatus = 1;
            if (isset($baseInfo['showStatus'])) {
                $showStatus = intval($baseInfo['showStatus']);
            }
   
            $fashTopGoods = self::getFashionTopGoodsStatus($uid, $showStatus, $isBeginSale);//異步
            //潮流尖貨狀態
            $goodsInfo['fashionTopGoods'] = array(
                        'getLimitedCode' => $fashTopGoods['getLimitedCode'],//限購碼狀態
                        'hasLimitedCode' => $fashTopGoods['hasLimitedCode'],//是否已經獲取限購碼
                        'limitedCodeSoldOut'=> $fashTopGoods['limitedCodeSoldOut'],//限購碼是否已經搶光
                        'getLimitedCodeDis' => $fashTopGoods['getLimitedCodeDis'],//限購碼是否失效
                     );
            if($fashTopGoods['soldOut']) {
                $goodsInfo['soldOut'] = $fashTopGoods['soldOut'];
                $totalStorageNum = 0;//改總數為已售磬
            } else {
                $goodsInfo['openSoon'] = $fashTopGoods['openSoon'];//即將開售
                $goodsInfo['dis'] = $fashTopGoods['dis'];//是否失效
                $goodsInfo['buyNow'] = $fashTopGoods['buyNow'];//是否立即購買
            }
        }
        
        $soldOut = $baseInfo['status'] == 0 || $totalStorageNum === 0;
        $notForSale = $baseInfo['attribute'] == 2;//非賣品
        $virtualGoods = $baseInfo['attribute'] == 3;//虛擬商品
        if (!$soldOut && !$notForSale && !$virtualGoods) {
             $goodsInfo['addToCart'] = true;
           //立即購買或者即將開售存在
            if((isset($goodsInfo['buyNow']) && $goodsInfo['buyNow'])  ||
               (isset($goodsInfo['openSoon']) && $goodsInfo['openSoon'])) {
                unset($goodsInfo['addToCart']);
            }
        }// 非賣品
        elseif ($notForSale) {
            $goodsInfo['notForSale'] = true;
        }
        // 已售磬
        elseif ($soldOut) {
            $goodsInfo['soldOut'] = true;
            unset($goodsInfo['fashionTopGoods']);
        }
        //虛擬商品
        else if($virtualGoods) {
            $goodsInfo['buyNow'] = true;//是否立即購買
            $goodsInfo['buyNowBase'] = Helpers::url('/ticket', array(), 'shopping');
            $goodsInfo['virtualGoods'] = $virtualGoods;
            if(isset($goodsInfo['salePrice'])) {
                $goodsInfo['advancePrice'] = $goodsInfo['salePrice'];//先行價格
                unset($goodsInfo['salePrice']);
            }
        }
        //去掉即將售罄
        if(empty($totalStorageNum) || $soldOut) {
            if(isset($goodsInfo['tags']['isFew'])) {
                unset($goodsInfo['tags']['isFew']);//去掉即將售罄
            }
        } 
        //分享相關
        $goodsInfo['weixinUrl'] = Helpers::url($_SERVER['REQUEST_URI'], array(),'item');
        $goodsInfo['sharedTitle'] = $goodsInfo['name'];
        $goodsInfo['shareImg'] = $goodsInfo['img'];
        $goodsInfo['shareDesc'] = $baseInfo['phrase'];
        
        //統計需要的商品信息
        $statGoodsInfo['uid'] = $uid;
        $statGoodsInfo['skn'] = $baseInfo['erpProductId'];
        $statGoodsInfo['productId'] = $productId;
        $statGoodsInfo['productName'] = str_replace("'", "’",$goodsInfo['name']);
        $statGoodsInfo['brandName'] = empty($goodsInfo['brandName'])? '' : str_replace("'", "’", $goodsInfo['brandName']);
        $statGoodsInfo['marketPrice'] = str_replace('¥', '', $goodsInfo['marketPrice']);
        if(isset($goodsInfo['salePrice'])) {
            $statGoodsInfo['salePrice'] = str_replace('¥', '', $goodsInfo['salePrice']);
        } else {
            $statGoodsInfo['salePrice'] = str_replace('¥', '', $goodsInfo['marketPrice']);
        }

        if (!empty($banner['brandId'])) {
            $domainBrand = BrandsModel::getBrandByDomain($banner['brandDomain']);
            if (!empty($domainBrand['type']) && !empty($domainBrand['shopId'])) {
                switch (intval($domainBrand['type'])) {
                    case 1: 
                        //多品店不顯示
                        $banner = array();
                        break;
                    case 2: 
                        //單品店顯示新版的店鋪banner
                        $basisData = ShopModel::basisTemplate($domainBrand['shopId']);
                        $banner['bgImg'] = empty($basisData['shopTopBanner']['banner']) ?
                            $banner['bgImg'] : $basisData['shopTopBanner']['banner'];
                        break;
                }
            }
        }

        $statGoodsInfo['imageUrl'] = $goodsInfo['img'];
        $statGoodsInfo['productUrl'] = $goodsInfo['weixinUrl'];
        $statGoodsInfo['smallSortId'] = $goodsInfo['smallSortId'];
        $statGoodsInfo['soldOut'] = intval($soldOut);
        return array('goodsInfo'=> $goodsInfo,'consultComment' => $consultComment, 'banner'=> $banner,'statGoodsInfo' => $statGoodsInfo);
    }

標異步的地方在函數中有十幾處,你怎樣解決呢?看懂業務后重構?

2.4 導成 javascript 的業務代碼

這是js代碼,使用 yield 解決問題

const detailDataPkg = (origin, uid, vipLevel, ua) => {

    return co(function*() {
        let result = {}; // 結果輸出

        // 商品名稱
        if (!origin.productName) {
            return result;
        }

        origin.uid = uid;
        result.name = origin.productName;
        result.skn = origin.erpProductId;
        result.productId = origin.id;
        result.maxSortId = origin.maxSortId;
        result.smallSortId = origin.smallSortId;
        result.promotionId = origin.isPromotion;
        result.goCartUrl = helpers.urlFormat('/shopping/cart');

        // 接口處理數據,設置並發請求數據
        yield setMultiResourceByProductBaseInfo(origin);

        let brandId = 0;

        if (origin.brand.id) {
            brandId = origin.brand.id;
        }

        // 處理收藏喜歡數據
        let favoriteData = yield getProductFavoriteData(uid, result.productId, brandId);

        // 商品標簽
        result.tags = getTagsDataByProductInfo(origin);

        //  商品促銷短語
        if (origin.salesPhase) {
            result.saleTip = origin.salesPhrase;
        }

        // 商品價格
        if (origin.productPriceBo) {
            result.marketPrice = origin.productPriceBo.formatMarketPrice;
            result.hasOtherPrice = true;
            result.salePrice = origin.productPriceBo.formatSalesPrice;
            if (result.marketPrice === result.salePrice) {
                delete result.salePrice;
                result.hasOtherPrice = false;
            }
        }

        //  VIP數據
        result.vipPrice = getVipDataByProductBaseInfo(origin, vipLevel, uid);

        //  促銷活動banner
        result.activity = yield getActivityDataByProductBaseInfo(origin);

        const C_VALUE = {
            type: '返YOHO幣',
            des: '每件返 ',
            rest: '個 YOHO幣'
        };

        if (origin.productPriceBo.yohoCoinNum && origin.productPriceBo.yohoCoinNum !== 0) {
            result.activity.push({
                type: C_VALUE.type,
                des: `${C_VALUE.des}${origin.productPriceBo.yohoCoinNum}${C_VALUE.rest}`
            });
        }

        // 上市期
        if (origin.expectArrivalTime) {
            result.arrivalDate = `${origin.expectArrivalTime}月`;
            result.presalePrice = origin.productPriceBo.formatSalesPrice;
            delete result.salePrice;
            result.hasOtherPrice = false;
        }

        // 商品咨詢和評論數據,當前為空
        let consultComment = getConsultCommentDataByProductInfo(origin);

        // 品牌信息
        let banner = null;

        if (origin.brand) {
            result.brandImg = helpers.image(origin.brand.brandIco, 47, 47);
            result.brandName = origin.brand.brandName;
            result.brandUrl = helpers.urlFormat('', {}, origin.brand.brandDomain);
            banner = yield getBrandDataByProductBaseInfo(origin);
            if (banner.isCollect && favoriteData.brand) {
                banner.isCollect = favoriteData.brand;
            }
        }

        // sku商品信息
        let skuData = getSkuDataByProductBaseInfo(origin);

        result.img = skuData.defaultImage;
        result.colors = skuData.skuGoods;
        let totalStorageNum = skuData.totalStorageNum;

        // 是否收藏
        result.isCollect = favoriteData.product;

        if (origin.isLimitBuy === 'y') {
            // 是否開售
            let isBeginSale = !!(origin.saleStatus && origin.saleStatus === 1);

            // 限購商品有關的展示狀態
            let showStatus = 1;

            if (origin.showStatus) {
                showStatus = parseInt(origin.showStatus);
            }

            let fashTopGoods = getFashionTopGoodsStatus(uid, showStatus, isBeginSale);


            result.fashTopGoods = {
                getLimitedCode: fashTopGoods.getLimitedCode, // 限購碼狀態
                hasLimitedCode: fashTopGoods.hasLimitedCode, // 是否已經獲取限購碼
                limitedCodeSoldOut: fashTopGoods.limitedCodeSoldOut, // 限購碼是否已經搶光
                getLimitedCodeDis: fashTopGoods.getLimitedCodeDis // 限購碼是否失效
            };

            if (fashTopGoods.soldOut) {
                result.soldOut = fashTopGoods.soldOut;
                totalStorageNum = 0; // 改總數為已售磬
            } else {
                result.openSoon = fashTopGoods.openSoon; // 即將開售
                result.dis = fashTopGoods.dis; // 是否失效
                result.buyNow = fashTopGoods.buyNow; // 是否立即購買
            }
        }

        let soldOut = !!(origin.status === 0 || totalStorageNum === 0);

        let notForSale = origin.attribute === 2; // 非賣品

        let virtualGoods = origin.attribute === 3; // 虛擬商品

        if (!soldOut && !notForSale && !virtualGoods) {
            result.addToCart = true;

            // 立即購買或者即將開售存在
            if (result.buyNow || result.openSoon) {
                delete result.addToCart;
            }
        } else if (notForSale) {
            // 非賣品
            result.notForSale = true;
        } else if (soldOut) {
            // 已售磬
            result.soldOut = true;
            delete result.fashTopGoods;
        } else if (virtualGoods) {
            // 虛擬商品
            result.buyNow = true; // 是否立即購買
            result.buyNowBase = helpers.urlFormat('/ticket', {}, 'shopping');
            result.virtualGoods = virtualGoods;
            if (result.salePrice) {
                result.advancePrice = result.salePrice; // 先行價格
                delete result.salePrice;
            }
        }

        // 去掉即將售罄
        if (totalStorageNum || soldOut) {
            if (result.tags.isFew) {
                delete result.tags.isFew; // 去掉即將售罄
            }
        }

        // 分享相關,產品的鏈接
        result.weixinUrl = helpers.urlFormat(origin.productUrl, {}, 'item');
        result.shareTitle = result.name;
        result.shareImg = result.img;
        result.shareDesc = result.phrase;


        // 統計需要的商品信息
        let statGoodsInfo = {};

        statGoodsInfo.uid = uid;
        statGoodsInfo.skn = origin.erpProductId;
        statGoodsInfo.productId = origin.id;
        statGoodsInfo.productName = result.name.replace('\'', '’');
        statGoodsInfo.brandName = (result.brandName || '').replace('\'', '’');
        statGoodsInfo.marketPrice = result.marketPrice.replace('¥', '');
        if (result.salePrice) {
            statGoodsInfo.salePrice = result.salePrice.replace('¥', '');
        } else {
            statGoodsInfo.salePrice = result.marketPrice.replace('¥', '');
        }

        if (banner.brandId) {
            let domainBrand = yield BrandData.getBrandByDomainAsync(banner.brandDomain);

            if (domainBrand.type && domainBrand.shopId) {
                switch (parseInt(domainBrand.type)) {
                    case 1:
                        {
                        // 多品店不顯示
                            banner = [];
                            break;
                        }
                    case 2:
                        {
                        // TODO:單品店顯示新版的店鋪banner,item.php 210
                            let basisData = ShopModel.basisTemplate(domainBrand.shopId);

                            banner.bgImg = basisData.shopTopBanner.banner || banner.bgImg;
                            break;
                        }

                }
            }
        }

        statGoodsInfo.imageUrl = result.img;
        statGoodsInfo.productUrl = result.weixinUrl;
        statGoodsInfo.smallSortId = result.smallSortId;
        statGoodsInfo.soldOut = parseInt(soldOut);

        return {
            goodsInfo: result,
            consultComment: consultComment,
            banner: banner,
            statGoodsInfo: statGoodsInfo
        };
    })();
};

大家可以看到,使用 yield 之后,代碼幾乎就是跟php完全一樣,我在寫的過程中,也了解到了業務,同時完成了任務,就是這樣。

3 結論

語言之爭沒有必要,重要的怎樣解決問題的。通過使用 yield 使我較快的完成這項任務,節省了大量的時間,而我也終於知道了為什么 yield 如此的有用,為什么大家想方設法的去解決回調問題,因為在沒有達到一定量級時,你確實沒有感覺到回調的痛苦。多了回調確實很麻煩。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM