2015-12-19 35 views
7

Tôi đang gặp phải sự cố mà tôi không hiểu đầy đủ. Tôi cảm thấy như có khả năng các khái niệm mà tôi chưa nắm bắt được, mã có thể được tối ưu hóa và có thể một lỗi được đưa ra để có biện pháp tốt.Không đồng bộ/Đang chờ không chờ đợi

Để đơn giản hóa đáng kể lưu lượng tổng thể:

  1. Một yêu cầu được thực hiện cho một API bên ngoài
  2. Các trở JSON đối tượng được phân tích và quét tài liệu tham khảo liên kết
  3. Nếu bất kỳ tài liệu tham khảo liên kết được tìm thấy, thêm yêu cầu được thực hiện để điền/thay thế các tham chiếu liên kết bằng dữ liệu JSON thực
  4. Sau khi tất cả các tham chiếu liên kết đã được thay thế, yêu cầu ban đầu được trả lại và sử dụng để tạo nội dung

Ở đây, là các yêu cầu ban đầu (# 1):

await Store.get(Constants.Contentful.ENTRY, Contentful[page.file]) 

Store.get được đại diện bởi:

async get(type, id) { 
    return await this._get(type, id); 
} 

nào gọi:

_get(type, id) { 
    return new Promise(async (resolve, reject) => { 
     var data = _json[id] = _json[id] || await this._api(type, id); 

     console.log(data) 

     if(isAsset(data)) { 
      resolve(data); 
     } else if(isEntry(data)) { 
      await this._scan(data); 

      resolve(data); 
     } else { 
      const error = 'Response is not entry/asset.'; 

      console.log(error); 

      reject(error); 
     } 
    }); 
} 

Cuộc gọi API :

_api(type, id) { 
    return new Promise((resolve, reject) => { 
     Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => { 
      if(error) { 
       console.log(error); 

       reject(error); 
      } else { 
       data = JSON.parse(data); 

       if(data.sys.type === Constants.Contentful.ERROR) { 
        console.log(data); 

        reject(data); 
       } else { 
        resolve(data); 
       } 
      } 
     }); 
    }); 
} 

Khi một mục được trả lại, nó được quét:

_scan(data) { 
    return new Promise((resolve, reject) => { 
     if(data && data.fields) { 
      const keys = Object.keys(data.fields); 

      keys.forEach(async (key, i) => { 
       var val = data.fields[key]; 

       if(isLink(val)) { 
        var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id); 

        this._inject(data.fields, key, undefined, child); 
       } else if(isLinkArray(val)) { 
        var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id)); 

        children.forEach((child, index) => { 
         this._inject(data.fields, key, index, child); 
        }); 
       } else { 
        await new Promise((resolve) => setTimeout(resolve, 0)); 
       } 

       if(i === keys.length - 1) { 
        resolve(); 
       } 
      }); 
     } else { 
      const error = 'Required data is unavailable.'; 

      console.log(error); 

      reject(error); 
     } 
    }); 
} 

Nếu tài liệu tham khảo liên kết được tìm thấy, yêu cầu bổ sung được thực hiện và sau đó kết quả JSON được tiêm vào JSON gốc ở vị trí của tài liệu tham khảo:

_inject(fields, key, index, data) { 
    if(isNaN(index)) { 
     fields[key] = data; 
    } else { 
     fields[key][index] = data; 
    } 
} 

Thông báo, tôi đang sử dụng async, awaitPromise 's Tôi tin vào trang viên dự định của họ. Điều gì sẽ xảy ra: Các cuộc gọi cho dữ liệu được tham chiếu (kết quả là _scan) sẽ xuất hiện sau khi yêu cầu ban đầu được trả về. Điều này kết thúc việc cung cấp dữ liệu không đầy đủ cho mẫu nội dung.

thông tin bổ sung liên quan đến xây dựng của tôi thiết lập:

+0

lời hứa Tại sao các bạn trộn và async/chờ: ' return new Promise (async (giải quyết, từ chối) => {...} '? Không nên là 'async _get (type, id) {...}' và không có lời hứa nào cả? – Shanoor

Trả lời

11

Tôi tin rằng sự cố là trong cuộc gọi forEach của bạn n _scan.Để tham khảo, xem đoạn này trong Taming the asynchronous beast with ES7:

Tuy nhiên, nếu bạn cố gắng sử dụng một chức năng async, sau đó bạn sẽ nhận được một lỗi tinh tế hơn:

let docs = [{}, {}, {}]; 

// WARNING: this won't work 
docs.forEach(async function (doc, i) { 
    await db.post(doc); 
    console.log(i); 
}); 
console.log('main loop done'); 

này sẽ biên dịch, nhưng vấn đề là rằng điều này sẽ in ra:

main loop done 
0 
1 
2 

gì đang xảy ra là các chức năng chính là thoát sớm, vì await là thực sự trong các tiểu chức năng. Hơn nữa, điều này sẽ thực hiện từng lời hứa đồng thời, đó không phải là những gì chúng tôi dự định.

Bài học là: hãy cẩn thận khi bạn có bất kỳ hàm nào bên trong hàm async của bạn. await sẽ chỉ tạm dừng chức năng gốc của nó, vì vậy hãy kiểm tra xem nó có đang thực hiện những gì bạn thực sự nghĩ nó đang hoạt động không.

Vì vậy, mỗi lần lặp lại cuộc gọi forEach đang chạy đồng thời; họ không thực hiện từng cái một. Ngay sau khi kết quả phù hợp với tiêu chí i === keys.length - 1 kết thúc, lời hứa được giải quyết và trả về _scan, mặc dù các chức năng không đồng bộ khác được gọi qua số forEach vẫn đang thực thi.

Bạn sẽ cần phải thay đổi các forEach đến một map để trả lại một mảng của những lời hứa, mà bạn có thể sau đó await* từ _scan (nếu bạn muốn thực hiện tất cả chúng đồng thời và sau đó gọi một cái gì đó khi họ đang làm tất cả), hoặc thực hiện chúng một lần tại một thời điểm nếu bạn muốn chúng thực thi theo trình tự.


Lưu ý rằng, nếu tôi đọc đúng, một số chức năng không đồng bộ của bạn có thể được đơn giản hóa một chút; hãy nhớ rằng, trong khi await nhập lệnh gọi hàm async trả về một giá trị, chỉ cần gọi nó trả về một lời hứa khác và trả lại giá trị từ hàm async giống như trả về lời hứa giải quyết giá trị đó theo hàm không async. Vì vậy, ví dụ, _get có thể là:

async _get(type, id) { 
    var data = _json[id] = _json[id] || await this._api(type, id); 

    console.log(data) 

    if (isAsset(data)) { 
    return data; 
    } else if (isEntry(data)) { 
    await this._scan(data); 
    return data; 
    } else { 
    const error = 'Response is not entry/asset.'; 
    console.log(error); 
    throw error; 
    } 
} 

Tương tự, _scan có thể được (giả sử bạn muốn forEach cơ quan để thực hiện đồng thời):

async _scan(data) { 
    if (data && data.fields) { 
    const keys = Object.keys(data.fields); 

    const promises = keys.map(async (key, i) => { 
     var val = data.fields[key]; 

     if (isLink(val)) { 
     var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id); 

     this._inject(data.fields, key, undefined, child); 
     } else if (isLinkArray(val)) { 
     var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id)); 

     children.forEach((child, index) => { 
      this._inject(data.fields, key, index, child); 
     }); 
     } else { 
     await new Promise((resolve) => setTimeout(resolve, 0)); 
     } 
    }); 

    await* promises; 
    } else { 
    const error = 'Required data is unavailable.'; 
    console.log(error); 
    throw error; 
    } 
} 
+0

Điều này rất hữu ích. Chỉ cần một lưu ý nhỏ, bạn vô tình cắt ra 'var val = data.fields [key];' – ArrayKnight

+0

@ArrayKnight Rất vui được sửa lỗi đánh máy, nhờ^_ ^ –

Các vấn đề liên quan