javascript:.replace 中的异步/等待

问题描述 投票:0回答:7

我按以下方式使用 async/await 函数

async function(){
  let output = await string.replace(regex, async (match)=>{
    let data = await someFunction(match)
    console.log(data); //gives correct data
    return data
  })
  return output;
}

但是返回的数据是一个promise对象。只是对如何在带有回调的函数中实现它感到困惑。

javascript async-await es6-promise ecmascript-2016
7个回答
50
投票

一个易于使用和理解的异步替换函数:

async function replaceAsync(str, regex, asyncFn) {
    const promises = [];
    str.replace(regex, (...args) => {
        promises.push(asyncFn(...args));
    });
    const data = await Promise.all(promises);
    return str.replace(regex, () => data.shift());
}

它会执行两次替换功能,因此请注意您是否执行了繁重的处理操作。但对于大多数用途来说,它非常方便。

像这样使用它:

replaceAsync(myString, /someregex/g, myAsyncFn)
    .then(replacedString => console.log(replacedString))

或者这个:

const replacedString = await replaceAsync(myString, /someregex/g, myAsyncFn);

不要忘记你的

myAsyncFn
必须返回一个承诺。

asyncFunction 的示例:

async function myAsyncFn(match) {
    // match is an url for example.
    const fetchedJson = await fetch(match).then(r => r.json());
    return fetchedJson['date'];
}

function myAsyncFn(match) {
    // match is a file
    return new Promise((resolve, reject) => {
        fs.readFile(match, (err, data) => {
            if (err) return reject(err);
            resolve(data.toString())
        });
    });
}

8
投票

原生

replace
方法不处理异步回调,你不能将它与返回promise的替换器一起使用。

但是我们可以编写自己的

replace
函数来处理 Promise:

async function(){
  return string.replace(regex, async (match)=>{
    let data = await someFunction(match)
    console.log(data); //gives correct data
    return data;
  })
}

function replaceAsync(str, re, callback) {
    // http://es5.github.io/#x15.5.4.11
    str = String(str);
    var parts = [],
        i = 0;
    if (Object.prototype.toString.call(re) == "[object RegExp]") {
        if (re.global)
            re.lastIndex = i;
        var m;
        while (m = re.exec(str)) {
            var args = m.concat([m.index, m.input]);
            parts.push(str.slice(i, m.index), callback.apply(null, args));
            i = re.lastIndex;
            if (!re.global)
                break; // for non-global regexes only take the first match
            if (m[0].length == 0)
                re.lastIndex++;
        }
    } else {
        re = String(re);
        i = str.indexOf(re);
        parts.push(str.slice(0, i), callback.apply(null, [re, i, str]));
        i += re.length;
    }
    parts.push(str.slice(i));
    return Promise.all(parts).then(function(strings) {
        return strings.join("");
    });
}

8
投票

这是 Overcl9ck 的答案的改进版和更现代的版本

async function replaceAsync(string, regexp, replacerFunction) {
    const replacements = await Promise.all(
        Array.from(string.matchAll(regexp),
            match => replacerFunction(...match)));
    let i = 0;
    return string.replace(regexp, () => replacements[i++]);
}

这需要更新的浏览器基线,因为

String.prototype.matchAll
,它于 2019 年全面落地(Edge 除外,它在 2020 年初通过基于 Chromium 的 Edge 获得了它)。但它至少同样简单,同时也更高效,仅在第一次进行“匹配”,而不是创建无用的字符串,也不会以昂贵的方式改变替换数组。

因此,不存在需要承诺的重载替换。因此,只需重申您的代码:

6
投票
async function(){ let data = await someFunction(); let output = string.replace(regex, data) return output; }

当然,如果你需要使用匹配值传递给异步函数,事情会变得有点复杂:

var sourceString = "sheepfoohelloworldgoocat"; var rx = /.o+/g; var matches = []; var mtch; rx.lastIndex = 0; //play it safe... this regex might have state if it's reused while((mtch = rx.exec(sourceString)) != null) { //gather all of the matches up-front matches.push(mtch); } //now apply async function someFunction to each match var promises = matches.map(m => someFunction(m)); //so we have an array of promises to wait for... //you might prefer a loop with await in it so that //you don't hit up your async resource with all //these values in one big thrash... var values = await Promise.all(promises); //split the source string by the regex, //so we have an array of the parts that weren't matched var parts = sourceString.split(rx); //now let's weave all the parts back together... var outputArray = []; outputArray.push(parts[0]); values.forEach((v, i) => { outputArray.push(v); outputArray.push(parts[i + 1]); }); //then join them back to a string... voila! var result = outputArray.join("");

这是 Overcl9ck 在 TypeScript 中实现的解决方案:

3
投票
const replaceAsync = async (str: string, regex: RegExp, asyncFn: (match: any, ...args: any) => Promise<any>) => { const promises: Promise<any>[] = [] str.replace(regex, (match, ...args) => { promises.push(asyncFn(match, ...args)) return match }) const data = await Promise.all(promises) return str.replace(regex, () => data.shift()) }

这是一种使用递归函数的替代方法:

1
投票
async function replaceAsync(str, regex, asyncFn) { const matches = str.match(regex); if (matches) { const replacement = await asyncFn(...matches); str = str.replace(matches[0], replacement); str = await replaceAsync(str, regex, asyncFn); } return str; }

还有另一个解决方案,这次是使用 TypeScript。与 

1
投票
类似,它通过使用

replace() 来代替,避免了许多其他解决方案中“语义上不寻常”的初始 match()

 调用。
async function replaceAsync(str: string, regex: RegExp, asyncFn: (match: string) => Promise<string>): Promise<string> {
  const promises = (str.match(regex) ?? []).map((match: string) => asyncFn(match));
  const data = await Promise.all(promises);
  return str.replace(regex, () => data.shift()!);
}


© www.soinside.com 2019 - 2024. All rights reserved.