在本教程中,您将了解 JavaScript Promise 链,该模式链接 Promise 并按顺序执行异步操作。

JavaScript Promise 链简介

有时,您想执行两个或多个异步的操作,其中下一个操作从上一步的结果开始。例如:

首先,创建一个在 3 秒获得数字 10 的 Promise:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});
请注意,这里使用 setTimeout() 函数模拟异步操作。

然后,调用 promise 的 then() 方法:

p.then((result) => {
    console.log(result);
    return result * 2;
});

一旦 promise 得到解决,传递给 then() 方法的回调就会执行。在回调中,我们显示promise 的结果并返回一个乘以二 result*2 的值。

因为 then() 方法返回一个 Promise 与一个值,所以您可以像这样在返回 Promise 时调用 then() 方法:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result);
    return result * 2;
}).then((result) => {
    console.log(result);
    return result * 3;
});

输出:

10
20

在此示例中,第一个 then() 方法中的返回值被传递给第二个 then() 方法。您可以连续调用 then() 方法多次,如下所示:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result); // 10
    return result * 2;
}).then((result) => {
    console.log(result); // 20
    return result * 3;
}).then((result) => {
    console.log(result); // 60
    return result * 4;
});

输出:

10
20
60

我们这样调用 then() 方法的方式通常被称为 Promise 链。

下图说明 Promise 链:

Promise 多个处理程序

当您对一个 Promise 多次调用 then() 方法时,这不是 Promise 链。例如:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result); // 10
    return result * 2;
})

p.then((result) => {
    console.log(result); // 10
    return result * 3;
})

p.then((result) => {
    console.log(result); // 10
    return result * 4;
});

输出:

10
10
10

在这个例子中,我们有一个 Promise 的多个处理程序。这些处理程序没有任何关系。此外,它们独立执行,不会像上面的 Promise 链那样将结果从一个传递到另一个。

下图说明具有多个处理程序的 Promise:

实际上,您很少会为一个 promise 使用多个处理程序。

返回 Promise

当您在 then() 方法中返回一个值时,then() 方法会返回一个 Promise  并立即将返回值解析为新值。

此外,您可以在 then() 方法中返回一个新的 Promise,如下所示:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(result * 2);
        }, 3 * 1000);
    });
}).then((result) => {
    console.log(result);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(result * 3);
        }, 3 * 1000);
    });
}).then(result => console.log(result));

输出:

10
20
60

此示例每 3 秒显示 10、20 和 60。此代码模式允许您按顺序执行一些任务。

下面修改上面的例子:

function generateNumber(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num);
    }, 3 * 1000);
  });
}

generateNumber(10)
  .then((result) => {
    console.log(result);
    return generateNumber(result * 2);
  })
  .then((result) => {
    console.log(result);
    return generateNumber(result * 3);
  })
  .then((result) => console.log(result));

Promise 链接语法

有时,您有多个要按顺序执行异步任务。此外,还需要将上一步的结果传递给下一步。在这种情况下,您可以使用以下语法:

step1()
    .then(result => step2(result))
    .then(result => step3(result))
    ...

如果需要先运行上一个任务而不传递结果给下一个任务,则可以使用以下语法:

step1()
    .then(step2)
    .then(step3)
    ...

假设你想按顺序执行以下异步操作:

  • 首先,从数据库中获取用户。
  • 其次,获取所选用户的服务。
  • 最后,从用户的服务中计算服务成本。

以下函数说明三个异步操作:

function getUser(userId) {
    return new Promise((resolve, reject) => {
        console.log('Get the user from the database.');
        setTimeout(() => {
            resolve({
                userId: userId,
                username: 'admin'
            });
        }, 1000);
    })
}

function getServices(user) {
    return new Promise((resolve, reject) => {
        console.log(`Get the services of ${user.username} from the API.`);
        setTimeout(() => {
            resolve(['Email', 'VPN', 'CDN']);
        }, 3 * 1000);
    });
}

function getServiceCost(services) {
    return new Promise((resolve, reject) => {
        console.log(`Calculate the service cost of ${services}.`);
        setTimeout(() => {
            resolve(services.length * 100);
        }, 2 * 1000);
    });
}

以下使用 promise 来进行顺序运行任务:

getUser(100)
    .then(getServices)
    .then(getServiceCost)
    .then(console.log);

输出

Get the user from the database.
Get the services of admin from the API.
Calculate the service cost of Email,VPN,CDN.
300
请注意,ES2017 引入 async/await 可帮助您编写比使用 promise 链技术更清晰的代码。

在本教程中,您了解了按顺序执行多个异步任务的 promise 链。