📜  JavaScript 承诺链(1)

📅  最后修改于: 2023-12-03 14:42:34.210000             🧑  作者: Mango

JavaScript Promise链

在 JavaScript 中,Promise 是一种非常重要的异步编程的方式,它可以解决回调地狱的问题,让代码更加简洁易懂。而 Promise 链则是 Promise 的一种组合方式,可以让多个 Promise 按顺序执行,让程序员更加容易掌控程序的执行流程。

Promise 链的基本结构

Promise 链是由多个 Promise 按顺序执行而成的,这些 Promise 执行时可以相互依赖,可以将上一个 Promise 的结果传递给下一个 Promise。

一个简单的 Promise 链可以这么写:

Promise.resolve()
  .then(() => {
    return 1;
  })
  .then((value) => {
    console.log(value); // 1
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 2
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 3
  });

在上面这个例子中,Promise 的链式调用是通过 then 方法实现的。在第一个 Promise 里,返回了一个值 1,这个值会被传递到第二个 Promise 里的回调函数中;同理,后续的 Promise 中也是如此。

Promise 链的使用案例

在实际的开发中,Promise 链可以帮助我们完成很多耗时的异步操作。例如,通过 Promise 链来上传用户头像:

const uploadAvatar = (file) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('post', '/api/user/avatar');
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => {
      reject(new Error('Network Error'));
    };
    xhr.send(file);
  });
}

const user = {
  avatar: null
};

uploadAvatar(file)
  .then(url => {
    user.avatar = url;
    return updateUser(user)
  })
  .then(() => {
    console.log('用户头像上传成功');
  })
  .catch(error => {
    console.error(error);
  });

在这个例子中,我们先通过 uploadAvatar 函数来上传用户头像。上传成功后,我们将头像的 URL 赋值给用户对象,然后再通过 updateUser 函数来更新用户信息。如果这个 Promise 链中的任何一个 Promise 被 reject 了,后续的 Promise 都会被跳过,直接进入 catch 中。

Promise 链的实现

在实现 Promise 链时,我们需要注意以下几点:

  1. 每个 Promise 的回调函数都应该返回一个新的 Promise 对象,并在 then 方法中调用;
  2. 在 then 方法中,如果当前的 Promise 结果被 resolve 了,则通过 Promise.resolve() 来生成一个新的 Promise 对象,并将结果传递给下一个 then 中的回调函数;如果当前的 Promise 结果被 reject 了,则通过 Promise.reject() 来生成一个新的 Promise 对象,并将错误传递给 catch 中;
  3. 在 then 方法中,如果回调函数中抛出了异常,则会自动被包装成 reject 状态的 Promise;

下面是一个简单的 Promise 链的实现:

class MyPromise {
  constructor(executor) {
    this.status = 'pending';
    this.data = null;
    this.onResolveCallbacks = [];
    this.setRejectCallbacks = [];

    const resolve = (data) => {
      if (this.status === 'pending') {
        this.status = 'resolved';
        this.data = data;
        this.onResolveCallbacks.forEach((callback) => {
          callback(this.data);
        });
      }
    };

    const reject = (error) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.data = error;
        this.setRejectCallbacks.forEach((callback) => {
          callback(this.data);
        });
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onResolve, onReject) {
    onResolve = typeof onResolve === 'function' ? onResolve : (value) => value;
    onReject = typeof onReject === 'function' ? onReject : (error) => { throw error };

    const promise = new MyPromise((resolve, reject) => {
      const resolveCallback = (result) => {
        try {
          const value = onResolve(result);
          if (value instanceof MyPromise) {
            value.then(resolve, reject);
          } else {
            resolve(value);
          }
        } catch (error) {
          reject(error);
        }
      };

      const rejectCallback = (error) => {
        try {
          const value = onReject(error);
          if (value instanceof MyPromise) {
            value.then(resolve, reject);
          } else {
            resolve(value);
          }
        } catch (error) {
          reject(error);
        }
      };

      if (this.status === 'resolved') {
        setTimeout(() => {
          resolveCallback(this.data);
        });
      }

      if (this.status === 'rejected') {
        setTimeout(() => {
          rejectCallback(this.data);
        });
      }

      if (this.status === 'pending') {
        this.onResolveCallbacks.push(resolveCallback);
        this.setRejectCallbacks.push(rejectCallback);
      }
    });

    return promise;
  }

  catch(onReject) {
    return this.then(null, onReject);
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    } else {
      return new MyPromise((resolve) => resolve(value));
    }
  }

  static reject(error) {
    return new MyPromise((resolve, reject) => reject(error));
  }
}
Promise.all 和 Promise.race

除了可以链式调用 Promise 之外,还可以通过 Promise.all 和 Promise.race 来组合多个 Promise 对象。

Promise.all 是一个将多个 Promise 对象合成为一个新的 Promise 对象的方法,所有 Promise 的状态都为 resolved 时,新的 Promise 状态才为 resolved,其中任意一个 Promise 的状态为 rejected 时,新的 Promise 的状态就变成 rejected。Example:

Promise.all([promise1, promise2, promise3])
  .then(([result1, result2, result3]) => {
    console.log(`结果1: ${result1}`);
    console.log(`结果2: ${result2}`);
    console.log(`结果3: ${result3}`);
  })
  .catch(error => {
    console.error(error);
  });

Promise.race 和 Promise.all 的用法基本类似,它的作用是多个 Promise 只要有一个完成,这个新的 Promise 状态就会改变。它们都是用于将多个异步操作按顺序执行的一种方式。

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    console.log(`最终结果: ${result}`);
  })
  .catch(error => {
    console.error(error);
  });
总结

通过学习 Promise 链的使用,我们可以更加容易地控制程序的执行流程,编写更加简洁优雅的异步代码;在 Promise 链中,我们需要注意回调函数的返回值,避免抛出异常导致的程序崩溃。同时,通过 Promise.all 和 Promise.race 的使用,我们可以将多个异步操作按顺序或者竞争的方式执行。

另外,在实际的开发中,可以直接使用第三方的 Promise 库,如 bluebird 等,这些库已经提供了非常丰富的异步 API,并且针对 Promise 链式调用做了大量的性能优化。