如何按顺序调用异步操作创建者?

时间:2018-01-03 16:57:52

标签: javascript reactjs redux es6-promise redux-thunk

Sourc代码

index.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/>;
    }
}

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

为了解释我创建的程序作为一个例子,当你按下回车键时,按下回车的时间会在几秒钟之后输出。

我希望在调用一个异步动作创建者之后调用下一个动作创建者。

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

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包装。

1 个答案:

答案 0 :(得分:1)

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

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

使用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
);