月度归档:2017年12月

react native实现拖拽/拖放

react native实现拖拽/拖放

首先初始化一个react native的项目,cd到你的目录下运行

react-native init rndd

创建一个新的组件./app/components/Draggable.js

import React, {
    Component,
} from 'react';
import {
    StyleSheet,
    Image,
    PanResponder,
    Animated,
} from 'react-native';
class Draggable extends Component{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <Animated.View style={styles.container}>
                <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
            </Animated.View>
        )
    }
}
export default Draggable;
const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        left: 100,
        top: 100,
    }
});

加上PanResponder

PanResponder的api可以上官网查看PanResponder

componentWillMount() {
    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        onPanResponderGrant: (e, gestureState) => {
        },
        onPanResponderMove: Animated.event([
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
        }
    });
}
render(){
    return (
        <Animated.View style={styles.container} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

响应拖拽事件

先在state中创建一个对象来记录拖拽的记录。

constructor(props) {
    super(props);
    this.state = {
        pan: new Animated.ValueXY()
    };
}

更新panHandler:

componentWillMount() {
    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        // 设置初始位置
        onPanResponderGrant: (e, gestureState) => {
            this.state.pan.setValue({x: 0, y: 0});
        },
        // 使用拖拽的偏移量来定位
        onPanResponderMove: Animated.event([
            null, {dx: this.state.pan.x, dy: this.state.pan.y},
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
        }
    });
}

更新render方法

render(){
    // 从state中取出pan
    const { pan } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

这个时候我们刷新用CMD+R刷新,发现已经可以拖动这个图片了,但是有个问题,第二次拖拽的时候又从原点开始移动,我们接下来解决这个问题。
我们有几个选择:

  1. 让拖动的元素在释放的时候回到原来的位置:
    修改PanResponder的onPanResponderRelease方法

    onPanResponderRelease: (e, {vx, vy}) => {
        Animated.spring(
            this.state.pan,
            {toValue: {x: 0, y: 0}}
        ).start();
    }
    
  2. 让拖动的元素停留在拖拽到的位置:
    修改PanResponder的onPanResponderGrant和onPanResponderRelease方法

    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        // 设置初始位置
        onPanResponderGrant: (e, gestureState) => {
            this.state.pan.setOffset({
                x: this.state.pan.x._value,
                y: this.state.pan.y._value
            });
            this.state.pan.setValue({x: 0, y: 0});
        },
        // 使用拖拽的偏移量来定位
        onPanResponderMove: Animated.event([
            null, {dx: this.state.pan.x, dy: this.state.pan.y},
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
            this.state.pan.flattenOffset();
        }
    });
    

我们再来加上拖拽时候放大的效果

先在state里加上一个scale用来记录放大的倍数

this.state = {
    pan: new Animated.ValueXY(),
    scale: new Animated.Value(1)
};

我们要在render里使用放大的值

render(){
    // 从state中取出pan和scale
    const { pan, scale } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY},  {scale}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

在拖拽的时候变大:

onPanResponderGrant: (e, gestureState) => {
    this.state.pan.setOffset({
        x: this.state.pan.x._value,
        y: this.state.pan.y._value
    });
    this.state.pan.setValue({x: 0, y: 0});
    Animated.spring(
        this.state.scale,
        { toValue: 1.3, friction: 3 }
    ).start();
},

在释放的时候缩小到原来的样子:

onPanResponderRelease: (e, {vx, vy}) => {
    this.state.pan.flattenOffset();
    Animated.spring(
        this.state.scale,
        { toValue: 1, friction: 3 }
    ).start();
}

加上拖拽的旋转效果:

先在state中加上一个rotate用来记录旋转的角度

this.state = {
    pan: new Animated.ValueXY(),
    scale: new Animated.Value(1),
    rotate: new Animated.Value(0)
};

在拖拽的时候旋转,需要在onPanResponderGrant里加上

Animated.timing(this.state.rotate, {
    toValue: 25, // 旋转25%,render里interpolate出deg的值
    duration: 300
}).start();

在释放的时候恢复原状,需要在onPanResponderRelease中加上

Animated.timing(this.state.rotate, {
    toValue: 0,
    duration: 300
}).start();

render函数里设置transform:

render(){
    // 从state中取出pan
    const { pan, scale } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 计算旋转
    const rotate = this.state.rotate.interpolate({
        inputRange: [0, 100],
        outputRange: ['0deg', '360deg']
    });
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY},  {scale}, {rotate}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

完整代码:

https://github.com/shengoo/rndd

import React, {
    Component,
} from 'react';
import {
    StyleSheet,
    Image,
    PanResponder,
    Animated,
} from 'react-native';
class Draggable extends Component{
    constructor(props){
        super(props);
        this.state = {
            pan: new Animated.ValueXY(),
            scale: new Animated.Value(1),
            rotate: new Animated.Value(0)
        };
    }
    componentWillMount() {
        this._panResponder = PanResponder.create({
            onMoveShouldSetResponderCapture: () => true,
            onMoveShouldSetPanResponderCapture: () => true,
            // 设置初始位置
            onPanResponderGrant: (e, gestureState) => {
                this.state.pan.setOffset({
                    x: this.state.pan.x._value,
                    y: this.state.pan.y._value
                });
                this.state.pan.setValue({x: 0, y: 0});
                Animated.spring(this.state.scale, {
                    toValue: 1.3,
                    friction: 3 }
                ).start();
                Animated.timing(this.state.rotate, {
                    toValue: 25,
                    duration: 300
                }).start();
            },
            // 使用拖拽的偏移量来定位
            onPanResponderMove: Animated.event([
                null, {dx: this.state.pan.x, dy: this.state.pan.y},
            ]),
            onPanResponderRelease: (e, {vx, vy}) => {
                this.state.pan.flattenOffset();
                // Animated.spring(
                //     this.state.pan,
                //     {toValue: {x: 0, y: 0}}
                // ).start();
                Animated.spring(
                    this.state.scale,
                    { toValue: 1, friction: 3 }
                ).start();
                Animated.timing(this.state.rotate, {
                    toValue: 0,
                    duration: 300
                }).start();
            }
        });
    }
    render(){
        // 从state中取出pan
        const { pan, scale } = this.state;
        // 从pan里计算出偏移量
        const [translateX, translateY] = [pan.x, pan.y];
        // 计算旋转
        const rotate = this.state.rotate.interpolate({
            inputRange: [0, 100],
            outputRange: ['0deg', '360deg']
        });
        // 设置transform为偏移量
        const imageStyle = {transform: [{translateX}, {translateY},  {scale}, {rotate}]};
        return (
            <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
                <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
            </Animated.View>
        )
    }
}
export default Draggable;
const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        left: 100,
        top: 100,
    }
});

使用n管理Node.js版本

Use n to manage your Node.js versions

Installation

  1. Install n through npm:(Recommended)
npm install -g n

Configurations

Since n‘s default path is /usr/local/n, it will need super user’s permission to modify file systems in the default path, we need to config n‘s path to user’s path.

config N_PREFIX

  1. edit bash profile
    vim ~/.bashrc
    
  2. add line
    export N_PREFIX=~/.n
    
  3. apply file
    source ~/.bashrc
    

config PATH

  1. edit bash profile
    vim ~/.bashrc
    
  2. add a line
    export PATH=$HOME/.n/bin:$PATH
    
  3. apply file
    source ~/.bashrc
    

Use

# Use or install a version of node
n 9.0.0
# Use or install the latest official release
n latest
# Use or install the stable official release
n stable
# Use or install the latest LTS official release
n lts

Type n to show list of versions.
And select a version by up down button.

$ n
    node/8.9.3
    node/9.0.0
    node/9.2.1
  ο node/9.3.0

React中的容器组件(Container Component)和展示组件(Presentational Component)

React中的容器组件(Container Component)和展示组件(Presentational Component)

React的思想里,UI=render(data)
所以,React的组件一般是完成两种功能:

  1. 读取数据
  2. 渲染界面

如果我们把两个功能都放在同一个组件内,那么这个组件做的事情就太多了,考虑到组件复用和业务变更,让一个组件只专注做一件事,我们可以把这个组件拆分成多个组件,让每个组件只专注做一件事,所以就衍生了一种React常用的模式:容器组件和展示组件。

容器组件

容器组件在外层,有以下特点:

  • 提供数据
  • 关注业务处理
  • 与状态管理工具交互
  • 有自己的状态(state)
  • 几乎没有dom和样式

展示组件

展示组件在容器组件内部,有以下特点:

  • 关注UI层
  • 有自己等dom和样式
  • 没有自己的状态
  • 不关心数据是如何获取和变化的
  • 通过props接收数据和回调函数
  • 不依赖其他组件
  • 不需要生命周期函数

展示组件一般可以使用纯函数的声明方式:

export default (props)=>(
    <div>Hello {props.name}</div>
);

这样做的好处

  • 分离关注,你可以更好的理解app和UI。
  • 更易复用,同样的展示组件可以在不同的状态源、数据源中使用。也可以封装成容器组件,在未来重用它们。
  • 数据结构和页面结构保持一致性。

iOS原生app中集成react-native组件

在iOS原生app中增加ReactNative

  1. 新建一个iOS的项目,打开XCode,新建一个项目,选择Tabbed App

  2. 运行一下看看效果

  3. 新建一个react-native项目,和iOS的项目中同一个目录下

    brew install node
    brew install watchman
    npm install -g react-native-cli
    react-native init ReactNativeProject
    
  4. 启动react-native服务器
    react-native start
    
  5. 安装CocoaPods
    brew install cocoapods
    
  6. 初始化CocoaPods,在你的iOS项目根目录里运行
    pod init
    
  7. 编辑生成的Podfile,增加react-native的pod
    # Uncomment the next line to define a global platform for your project
    # platform :ios, '9.0'
    target 'demo' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!
    # Pods for demo
    pod 'React', :path => ‘../ReactNativeProject/node_modules/react-native', :subspecs => [
    'Core',
    'CxxBridge', # Include this for RN >= 0.47
    'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
    'RCTText',
    'RCTNetwork',
    'RCTWebSocket', # needed for debugging
    # Add any other subspecs you want to use in your project
    ]
    pod "yoga", :path => "../ReactNativeProject/node_modules/react-native/ReactCommon/yoga"
    pod 'DoubleConversion', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
    pod 'GLog', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/GLog.podspec'
    pod 'Folly', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/Folly.podspec'
    target 'demoTests' do
    inherit! :search_paths
    # Pods for testing
    end
    target 'demoUITests' do
    inherit! :search_paths
    # Pods for testing
    end
    end
    
  8. 运行pod install安装依赖,得到以下输出:
    $ pod install
    Setting up CocoaPods master repo
    $ /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
    Cloning into 'master'...
    remote: Counting objects: 1799117, done.
    remote: Compressing objects: 100% (377/377), done.
    remote: Total 1799117 (delta 157), reused 35 (delta 35), pack-reused 1798692
    Receiving objects: 100% (1799117/1799117), 500.73 MiB | 320.00 KiB/s, done.
    Resolving deltas: 100% (981561/981561), done.
    Checking out files: 100% (203691/203691), done.
    Setup completed
    Analyzing dependencies
    Fetching podspec for `DoubleConversion` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`
    Fetching podspec for `Folly` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/Folly.podspec`
    Fetching podspec for `GLog` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/GLog.podspec`
    Fetching podspec for `React` from `../ReactNativeProject/node_modules/react-native`
    Fetching podspec for `yoga` from `../ReactNativeProject/node_modules/react-native/ReactCommon/yoga`
    Downloading dependencies
    Installing DoubleConversion (1.1.5)
    Installing Folly (2016.09.26.00)
    Installing GLog (0.3.4)
    Installing React (0.51.0)
    Installing boost (1.59.0)
    Installing yoga (0.51.0.React)
    Generating Pods project
    Integrating client project
    
  9. 打开iOS项目目录下的demo.xcworkspace

  10. 在AppDelegate.swift文件中引入React

    import React
    
  11. 声明react-native组件的UIViewController并加入到tab中
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -&gt; Bool {
    // Override point for customization after application launch.
    var tab = self.window?.rootViewController as! UITabBarController
    let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")
    let rootView = RCTRootView(
    bundleURL: jsCodeLocation,
    moduleName: "ReactNativeProject",
    initialProperties: nil,
    launchOptions: nil
    )
    let vc = UIViewController()
    vc.view = rootView
    vc.title = "rn"
    tabbar.viewControllers?.append(vc)
    return true
    }
    
  12. 设置允许localhost的http访问

  13. 运行看看效果

代码: https://github.com/shengoo/iOS-rn-h5

使用Webpack loader打包css文件

Loaders

style-loader

style-loader adds CSS to the DOM by injecting a style tag.

css-loader

The css-loader interprets @import and url() like import/require() and will resolve them.

postcss-loader

Use it after css-loader and style-loader, but before other preprocessor loaders like e.g sass|less|stylus-loader, if you use any.

Autoprefixer

Autoprefixer is a service for managing vendor prefixes. It adds missing prefixes and deletes obsolete ones.

Extract CSS

Extract text from a bundle, or bundles, into a separate file.
Needed in production mode.

Configuration

Use in js:

import from './file.css'
import from './file.less'
import from './file.sass'

Development

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
              plugins: [
                autoprefixer({
                  browsers: ['> 5%']
                })
              ]
            }
                    }
                ]
            },
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
              plugins: [
                autoprefixer({
                  browsers: ['> 5%']
                })
              ]
            }
                    },
                    'less-loader'
                ]
            }
        ]
    }
}

Production

module.exports = {
    module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
                        {
              loader: 'css-loader',
              options: {
                importLoaders: 1,
                minimize: true,
                sourceMap: true,
              },
            },
            {
              loader: 'postcss-loader',
              options: {
                plugins: [
                  autoprefixer({
                    browsers: ['> 5%']
                  })
                ]
              }
            },
          ]
        })
      },
      {
        test: /\.less$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
                        {
              loader: 'css-loader',
              options: {
                importLoaders: 1,
                minimize: true,
                sourceMap: true,
              },
            },
            {
              loader: 'postcss-loader',
              options: {
                plugins: [
                  autoprefixer({
                    browsers: ['> 5%']
                  })
                ]
              }
            },
            'less-loader']
        })
      },
    ]
  },
  plugins: [
    // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
    new ExtractTextPlugin({filename: '[name].[hash:8].css'})
  ],
}

Mac开发环境配置

Mac开发配置

System Preferences

 > 关于本机 > 软件更新

触控板

  •  > 系统偏好设置 > 触控板
    • 光标与点击
      • ✓ 查询与数据监测器 > 用一个手指用力点按
      • ✓ 辅助点按 > 用两个手指点按或轻点
      • ✓ 轻拍来点按
      • ✓ 静默点按(声音变小)
      • ✓ 用力点按和触觉反馈
    • 滚动缩放
      • ✓ 默认全选
    • 更多手势
      • ✓ 默认全选
  •  > 系统偏好设置 > 辅助功能
    启用三指拖移

  •  > 系统偏好设置 > 键盘 > 快捷键
    打开所有控制

Dock

  •  > 系统偏好设置 > Dock
    • ✓ 将窗口最最小化为应用程序图标
    • ✓ 自动显示和隐藏 Dock

Finder

  • Finder > 显示

    • 显示标签页栏
    • 显示路径栏
    • 显示状态栏
  • Finder > 偏好设置
    • 通用
      • 开启新 Finder 窗口时打开:HOME「用户名」目录
    • 边栏
      • 添加 HOME「用户名」目录 和 创建代码文件目录
      • 将 共享的(shared) 和 标记(tags) 目录去掉

Show/Hide Hidden Files the Long Way

  1. Open Terminal found in Finder > Applications > Utilities
  2. In Terminal, paste the following: defaults write com.apple.finder AppleShowAllFiles YES
  3. Press return
  4. Hold the ‘Option/alt’ key, then right click on the Finder icon in the dock and click Relaunch.

quicklook plugins

// run after brew cask installed
brew cask install qlcolorcode
brew cask install qlstephen
brew cask install qlmarkdown
brew cask install quicklook-json
brew cask install qlprettypatch
brew cask install quicklook-csv
brew cask install betterzipql
brew cask install webpquicklook
brew cask install suspicious-package

Scroll Reverser

当你在浏览一个很长的网页时,你看完了当前显示的内容,想要看后续的内容,你可以在 Trackpad 上双指上滑,或者鼠标滚轮向上滚动。这是被称作“自然”的滚动方向。
然而在 Windows 里鼠标滚动的行为是相反的:鼠标滚轮向下滚动才会让浏览器显示后续的内容,向上滚动会达到页面的顶部。你可以在 OS X 的系统偏好设置里修改(选择System Preferences >Trackpad,在Scroll & Zoom标签页中不选中Scroll direction: natural),但是这样会同时改变鼠标滚轮的方向和 Trackpad 的方向。
要想只改变鼠标滚轮的方向,而保持 Trackpad 依旧是“自然”的,我们需要 Scroll Reverser:
brew cask install scroll-reverser

vim配置

vi ~/.vimrc

color desert "颜色设置
syntax on "语法高亮
set number "自动显示行号
set cursorline "突出显示当前行
set ts=4 "设置tab长度为4
set shiftwidth=4 "设定 << 和 >> 命令移动时的宽度为 4

Homebrew

  • Homebrew : package manager for macOS
    包管理工具,官方称之为The missing package manager for OS X。
    安装步骤:先打开 Terminal 应用,输入:
    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    有了 brew 以后,要下载工具,比如 MySQL、Gradle、Maven、Node.js 等工具,就不需要去网上下载了,只要一行命令就能搞定:
    brew install mysql gradle maven node

  • Homebrew-Cask
    brew-cask 允许你使用命令行安装 OS X 应用。比如你可以这样安装 Chrome:brew cask install google-chrome。还有 Evernote、Skype、Sublime Text、VirtualBox 等都可以用 brew-cask 安装。
    安装:
    brew tap caskroom/cask

iterm2

iTerm2 是最常用的终端应用,是 Terminal 应用的替代品。提供了诸如Split Panes等一群实用特性。

安装:
brew cask install iterm2

Git

XCode会安装git,无需单独安装。

  • SourceTree
    SourceTree 是 Atlassian 公司出品的一款优秀的 Git 图形化客户端。如果你发现命令行无法满足你的要求,可以试试 SourceTree。
    安装:
    brew cask install sourcetree
    用 brew-cask 安装会自动增加命令行工具stree到$PATH里。在命令行中输入stree可以快速用 SourceTree 打开当前 Git 仓库。详细用法请参见stree –help。

Text Editors

  • Sublime Text
    brew cask install sublime-text

  • Visual Studio Code
    brew cask install visual-studio-code

Softwares

  • Browsers

    • Chrome
      brew cask install google-chrome

  • dropbox
    brew cask install dropbox

  • postman
    brew cask install postman

  • teamviewer
    brew cask install teamviewer

  • shadowsocksx-ng
    brew cask install shadowsocksx-ng

  • Alfred
    brew cask install alfred

  • BetterSnapTool
    https://itunes.apple.com/cn/app/bettersnaptool/id417375580?mt=12

  • Go2Shell
    https://itunes.apple.com/cn/app/go2shell/id445770608?mt=12
    brew cask install go2shell

  • Sip : Color picker
    brew cask install sip

  • Snip
    https://itunes.apple.com/cn/app/snip/id512505421?mt=12
    brew cask install snip

  • The Unarchiver
    https://itunes.apple.com/cn/app/the-unarchiver/id425424353?mt=12
    brew cask install the-unarchiver

NodeJS

brew install nodejs
NodeJS Packages

  • n : Node version management
    npm install -g n

  • change nodejs to LTS version

    mkdir /usr/local/n
    sudo mkdir /usr/local/n
    sudo chown -R $(whoami) /usr/local/n
    n lts
    

Java

  • install java 8 instead of java 9
    brew tap caskroom/versions
    brew cask install java8
    
  • Maven
    brew install maven

IDE

  • XCode
    https://itunes.apple.com/cn/app/xcode/id497799835?mt=12

    • CocoaPods
      sudo gem install cocoapods

  • Android Studio
    brew cask install android-studio

  • IntelliJ Idea
    brew cask install intellij-idea

  • WebStorm

    brew cask install webstorm

让很多Promise一个接一个进行

Promise是我们处理异步操作的时候经常用到的。
有的时候我们会需要顺序执行很多异步操作,如果操作比较少的时候还可以写成a.then(b).then(c)...,可是如果异步操作很多的时候就不能这样写了。
可以利用数组的reduce函数达到同样的效果,这个时候不管有多少异步操作都可以连在一起了。
代码如下:

function waitPromise(time) {
    console.log(time);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('resolved');
        }, time);
    });
}
function log(data) {
    return new Promise((resolve, reject) => {
        console.log(data + '@' + (new Date().getSeconds()));
        resolve();
    });
}
var ps = [];
for (var i = 0; i < 3; i++) {
    let time = (i + 1) * 1000;
    ps.push(() => waitPromise(time));
    ps.push(log);
}
console.log('started' + '@' + (new Date().getSeconds()));
var p = Promise.resolve();
ps.reduce((p, c) => {
    return p.then(c)
}, p).then(() => {
    console.log('all finished');
}).catch(reject => {
    console.log('reject', reject)
});

输出结果:

started@59
1000
resolved@0
2000
resolved@2
3000
resolved@5
all finished