javascript - 如何按顺序调用异步 Action 创建者?

标签 javascript reactjs redux es6-promise redux-thunk

源代码

索引.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import ReduxThunk from 'redux-thunk';
import reducer from './redux';

const body = document.querySelector('body'),
      composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose,
      store = createStore(reducer, composeEnhancers(applyMiddleware(ReduxThunk)));

ReactDOM.render(<Provider store={store}><App/></Provider>, body);

App.js

import React from 'react';
import Shortcut from './Shortcut';

export default class App extends React.PureComponent {
    render() {
        return <Shortcut/>;
    }
}

快捷方式.js

import React from 'react';
import { connect } from 'react-redux';
import { print_date_async } from './redux';

class Shortcut extends React.PureComponent {
    componentDidMount() {
        window.addEventListener('keydown', (event) => {
            if (event.keyCode === 13) {
                this.props.print_date_async({ date: new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1") });
            }
        });
    }

    render () {
        return null;
    }
}

function mapDispatchToProps(dispatch) {
    return {
        print_date_async: (date) => dispatch(print_date_async(date))
    };
}

Shortcut = connect(undefined, mapDispatchToProps)(Shortcut);

export default Shortcut;

redux.js

import { createAction, handleActions } from 'redux-actions';

export const print_date = createAction('print_date');

export function print_date_async (payload) {
    return async (dispatch) => {
        try {
            await wait_async();
            dispatch(print_date({ date:payload.date }));
        }
        catch (exeption) {
            console.error(exeption);
        }
    };
}

const initial_state = { };

export default handleActions({
    print_date: (state, action) => {
        console.log(action.payload.date);
        return { ...state }
    }
}, initial_state);

function wait_async (number) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, Math.floor(Math.random() * 10000)); // 0000 ~ 9999
    });
};

问题

https://codesandbox.io/s/l7y4rn61k9

以我创建的程序为例进行说明,当您按下回车键时,您按下回车键的时间会在随机秒后输出。

我想在调用一个异步 Action 创建器之后调用下一个 Action 创建器。

如果按住回车键,第一次按下的结果也可以稍后打印。

01:42:48
01:42:48
01:42:47
01:42:47
01:42:47
01:42:47
01:42:48

我考虑过导出变量来检查状态,但我不喜欢它。我也不喜欢检查按键之间的间隔。

我想按照下面的方式来做,但是实现起来并不容易。如果你知道这件事,请回答。感谢阅读!

window.addEventListener('keydown', (event) => {
    if (event.keyCode === 13) {
        if (!this.proceeding) {
            this.proceeding = true;
            (async () => {
                await this.props.print_date_async({ date: new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1") });
                this.proceeding = false;
            })();
        }
    }
});

解决

我只需要在使用 redux-thunk 时返回 promise 对象。

export function print_date_async (payload) {
    return (dispatch) => new Promise(async (resolve, reject) => {
        try {
            await wait_async();
            dispatch(print_date({ date:payload.date }));
            resolve();
        }
        catch (exeption) {
            console.error(exeption);
            reject(exeption);
        }
    });
}

我知道 async() 返回 promise 对象,所以你不必用 promise 包装。

最佳答案

问题是您有一个用户输入流,它创建了一个异步值流。这些值可以按照与用户输入顺序不同的顺序解析。

例如:用户点击 A、B 和 C,这会创建 Promise A、B 和 C,但它们按 C、B 和 A 的顺序解析。您希望它们按与用户操作相同的顺序解析,对吗?

使用 promises 解析来排序或加入用户生成的流非常复杂,也许 Reactive x 可以解决这个问题,但下面的代码应该可以做到:

const resolveOrderedAndFast = (function(promiseInfos,requestIndex){
  const lastCall = {};
  return fastHandler => orderedHandler => promise => {
    requestIndex++;
    promise.then(fastHandler);
    promiseInfos.push([promise,orderedHandler,requestIndex]);


    //only resolve when it's last call
    const last = () => {
      lastCall.id={};
      var check = lastCall.id;
      return promise.then(
        resolve=>
          Promise.all(
            promiseInfos
            .sort((x,y)=>x[2]-y[2])
            .map(([p,fn,requestIndex])=>{
              return p.then(
                ([r,seconds])=>{
                  return [r,seconds,fn,requestIndex];
                }
              )
            })
          ).then(
            (resolves)=>{
              if(check===lastCall.id){
                resolves.forEach(
                  ([r,seconds,fn,requestIndex])=>
                    fn([r,seconds,requestIndex])
                );
                promiseInfos=[];
                requestIndex=0;
              }else{
                //ignore, no problem
              }
            }
          )
      );
    };
    last();//only resolve the last call to this function
  };
}([],0))

const later = resolveValue => {
  const values = ["A","B","C"];
  const index = values.indexOf(resolveValue);
  return new Promise(
    (resolve,reject)=>
      setTimeout(
        x=>resolve([resolveValue,(4-(index*2))])
        ,(4-(index*2))*1000
      )
  )
};
const fastHandler = val => ([resolve,seconds])=>
  console.log(
    val,
    "FAST HANDLER --- resolved with:",
    resolve,
    "in",seconds,"seconds"
  );
const orderedHandler = val => ([resolve,seconds,i])=>
  console.log(
    "Call id:",
    i,
    "ORDRED HANDLER --- resolved with:",
    resolve,
    "in",seconds,"seconds"
  );
const valueArray = ["A","B","C","A","B","C","A"];
console.log("making request:",valueArray);
valueArray
.forEach(
  val=>
  resolveOrderedAndFast
    (fastHandler(val))
    (orderedHandler(val))
    (later(val))
);

setTimeout(
  ()=>
  console.log("making more requests:",valueArray) ||
  valueArray
    .forEach(
      val=>
      resolveOrderedAndFast
        (fastHandler(val))
        (orderedHandler(val))
        (later(val))
    )
  ,500
);

这是一个更简单的版本,对它的作用有更多评论:

const Fail = function(reason){this.reason=reason;};
const resolveOrderedAndFast = (function(preIndex,postIndex,stored){
  return fastHandler => orderedHandler => promise => {
    //call this function before the promise resolves
    const pre = p=> {
      preIndex++;
      p.then(fastHandler);
      (function(preIndex,handler){//set up post when promise resolved
        //add pre index, this will be 1,2,3,4
        //  but can resolve as 2,4,3,1
        p.then(r=>post([r,handler,preIndex]))
        //because the promises are stored here you may want to catch it
        //  you can then resolve with special Fail value and have orderedHandler
        //  deal with it
        .catch(e=>
          post(
            [
              new Fail([e,r,preIndex]),//resolve to Fail value if rejected
              handler,
              preIndex
            ]
          )
        )
      })(preIndex,orderedHandler);//closure on index and ordered handler
    };
    //this will handle promise resolving
    const post = resolve=>{
      //got another resolved promise
      postIndex++;
      //store the details (it's resolve value, handler and index)
      stored.push(resolve);
      //deconstruct the resole value
      const [r,handler,i]=resolve;
      //the index of the resolve is same as current index
      //  that means we can start calling the resolvers
      if(i===postIndex){
        //sort the stored by their indexes (when they stared)
        stored = stored
        .sort((x,y)=>x[2]-y[2])
        //filter out all the ones that can be resolved
        .filter(
          ([r,handler,i])=>{
            //can be resolved, no promises have yet to be resolved
            //  before this index
            if(i<=postIndex){
              //increase the number of indexes procssed (or stored)
              postIndex++;
              //resolve the value again (so we don't get errors)
              Promise.resolve([r,i]).then(handler);
              return false;//no need to "resolve" this again, filter it out
            }
            return true;
          }
        )
      }
    };
    pre(promise);
  };
})(0,0,[])//passing in pre and post index and stored


//demo on how to use it:
const later = resolveValue => {
  const values = ["A","B","C"];
  const index = values.indexOf(resolveValue);
  return new Promise(
    (resolve,reject)=>
      setTimeout(
        x=>resolve([resolveValue,(4-(index*2))])
        ,(4-(index*2))*1000
      )
  )
};
const fastHandler = val => ([resolve,seconds])=>
  console.log(
    val,
    "FAST HANDLER --- resolved with:",
    resolve,
    "in",seconds,"seconds"
  );
const orderedHandler = val => ([[resolve,seconds],i])=>
  console.log(
    "Call id:",
    i,
    "ORDRED HANDLER --- resolved with:",
    resolve,
    "in",seconds,"seconds"
  );
const valueArray = ["A","B","C","A","B","C","A"];
console.log("making request:",valueArray);
valueArray
.forEach(
  val=>
  resolveOrderedAndFast
    (fastHandler(val))
    (orderedHandler(val))
    (later(val))
);

setTimeout(
  ()=>
  console.log("making more requests:",valueArray) ||
  valueArray
    .forEach(
      val=>
      resolveOrderedAndFast
        (fastHandler(val))
        (orderedHandler(val))
        (later(val))
    )
  ,500
);

关于javascript - 如何按顺序调用异步 Action 创建者?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48081810/

相关文章:

javascript - 将 POST react 到 Node 服务器并处理 JSON 响应

reactjs - 为什么 Typescript 认为我的枚举未定义?

javascript - API更新后如何更改商店状态?

javascript - Loopback自定义远程方法错误回调

javascript - Ember UI 在测试期间未触发 Ember Controller 操作

reactjs - 与 Storybook v5 一起使用 antd for React

reactjs - 如何知道react-router是否可以返回并在react应用程序中显示后退按钮

javascript - 为什么我的 User.props 总是未定义? React-Native 和 Redux

javascript - 如何防止 form.trigger ('submit' 上的页面刷新?

javascript - 变量 x 曾经是真的吗?