javascript: Async / ожидание в .replace

Я использую функцию 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;
}

Но возвращенные данные - это объект обещания. Просто запутались, как это должно быть реализовано в таких функциях с обратным вызовом.


person ritz078    schedule 10.11.2015    source источник
comment
Возвращаемое значение из функции async всегда является объектом Promise, который разрешается с возвращенным output (или отклоняется с выданной ошибкой).   -  person Madara's Ghost    schedule 10.11.2015
comment
Вам интересно, почему output - это обещание? Мне неясно, в чем ваша проблема. Обратите внимание: если string.replace буквально String.prototype.replace, это не сработает. .replace ожидает, что обратный вызов будет нормальной функцией, а не асинхронной функцией.   -  person Felix Kling    schedule 10.11.2015


Ответы (3)


Простая функция для использования и понимания некоторых асинхронных замен:

async function replaceAsync(str, regex, asyncFn) {
    const promises = [];
    str.replace(regex, (match, ...args) => {
        const promise = asyncFn(match, ...args);
        promises.push(promise);
    });
    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())
        });
    });
}
person Overcl9ck    schedule 30.12.2017
comment
Это работает только при использовании замены для перебора совпадений. Это не работает для замены. - person Jack Giffin; 13.10.2018
comment
Хотя это так. Он выполняет итерацию и заменяет. - person Overcl9ck; 15.10.2018
comment
Мне очень нравится это решение, красивое и простое! - person Livewire; 18.02.2019

собственный replace метод не работает с асинхронными обратными вызовами вы не можете использовать его с заменителем, который возвращает обещание.

Однако мы можем написать нашу собственную replace функцию, которая работает с обещаниями:

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("");
    });
}
person Bergi    schedule 10.11.2015

Итак, нет необходимости в замене, требующей обещания. Так что просто переформулируйте свой код:

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("");
person spender    schedule 10.11.2015
comment
Я обновил вопрос. Мне нужно передать согласованный элемент функции, так что это невозможно. - person ritz078; 10.11.2015
comment
@ ritz078 Я думал, ты это пропустил. Может быть, моя правка более полезна? - person spender; 10.11.2015